rrt/crates/rrt-runtime/src/smp.rs

18347 lines
733 KiB
Rust

use std::collections::BTreeMap;
use std::fs;
use std::path::Path;
use std::sync::OnceLock;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use crate::{
RuntimeCargoClass, RuntimeCargoPriceTarget, RuntimeCargoProductionTarget,
RuntimeChairmanMetric, RuntimeChairmanTarget, RuntimeCompanyConditionTestScope,
RuntimeCompanyControllerKind, RuntimeCompanyMarketState, RuntimeCompanyMetric,
RuntimeCompanyTarget, RuntimeCondition, RuntimeConditionComparator, RuntimeEffect,
RuntimeEventRecordTemplate, RuntimePlayerConditionTestScope, RuntimePlayerTarget,
RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts,
};
pub const SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION: u32 = 0x03ec;
const PREAMBLE_U32_WORD_COUNT: usize = 16;
const MIN_ASCII_RUN_LEN: usize = 8;
const ASCII_PREVIEW_CHAR_LIMIT: usize = 160;
const TAG_OFFSET_SAMPLE_LIMIT: usize = 8;
const EARLY_ZERO_RUN_THRESHOLD: usize = 16;
const EARLY_PREVIEW_BYTE_LIMIT: usize = 32;
const EARLY_ALIGNED_WORD_WINDOW_COUNT: usize = 8;
const SPECIAL_CONDITIONS_OFFSET: usize = 0x0d64;
const SPECIAL_CONDITION_COUNT: usize = 36;
const SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT: usize = 35;
const SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT: usize = 50;
const SMP_ALIGNED_RUNTIME_RULE_KNOWN_EDITOR_RULE_COUNT: usize = 49;
const SMP_ALIGNED_RUNTIME_RULE_RUNTIME_OBJECT_OFFSET: usize = 0x4a7f;
const SMP_ALIGNED_RUNTIME_RULE_END_OFFSET: usize =
SPECIAL_CONDITIONS_OFFSET + SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT * 4;
const POST_SPECIAL_CONDITIONS_SCALAR_OFFSET: usize = 0x0df4;
const POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET: usize = 0x0f30;
const POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET: usize = 0x0f58;
const POST_SPECIAL_CONDITIONS_SCALAR_OVERLAP_END_OFFSET: usize =
SMP_ALIGNED_RUNTIME_RULE_END_OFFSET;
const POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET: usize = SMP_ALIGNED_RUNTIME_RULE_END_OFFSET;
const POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_OFFSET: usize =
SMP_ALIGNED_RUNTIME_RULE_RUNTIME_OBJECT_OFFSET
+ (POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET - SPECIAL_CONDITIONS_OFFSET);
const POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_END_OFFSET: usize =
SMP_ALIGNED_RUNTIME_RULE_RUNTIME_OBJECT_OFFSET
+ (POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET - SPECIAL_CONDITIONS_OFFSET);
const POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_OFFSET: usize = 0x4b47;
const POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_LEN: usize = 0x12c;
const POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_END_OFFSET: usize =
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_OFFSET
+ POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_LEN;
const POST_TEXT_FIELD_NEIGHBORHOOD_OFFSET: usize = 0x0f59;
const POST_TEXT_FIELD_NEIGHBORHOOD_END_OFFSET: usize = 0x0f75;
const POST_TEXT_FIELD_0_RUNTIME_OBJECT_OFFSET: usize = 0x4c74;
const POST_TEXT_FIELD_1_RUNTIME_OBJECT_OFFSET: usize = 0x4c78;
const POST_TEXT_FIELD_2_RUNTIME_OBJECT_OFFSET: usize = 0x4c7c;
const POST_TEXT_FIELD_3_RUNTIME_OBJECT_OFFSET: usize = 0x4c80;
const POST_TEXT_FIELD_4_RUNTIME_OBJECT_OFFSET: usize = 0x4c88;
const POST_TEXT_FIELD_5_RUNTIME_OBJECT_OFFSET: usize = 0x4c8c;
const POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_OFFSET: usize = 0x4c80;
const POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_OFFSET: usize = 0x4c8c;
const POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_FILE_OFFSET: usize =
SPECIAL_CONDITIONS_OFFSET
+ (POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_OFFSET
- SMP_ALIGNED_RUNTIME_RULE_RUNTIME_OBJECT_OFFSET);
const POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_FILE_OFFSET: usize =
SPECIAL_CONDITIONS_OFFSET
+ (POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_OFFSET
- SMP_ALIGNED_RUNTIME_RULE_RUNTIME_OBJECT_OFFSET);
const LOCOMOTIVE_POLICY_NEIGHBORHOOD_OFFSET: usize = 0x0f78;
const LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET: usize = 0x0fa7;
const LOCOMOTIVE_POLICY_FIELD_NEG3_RUNTIME_OBJECT_OFFSET: usize = 0x4ca2;
const LOCOMOTIVE_POLICY_FIELD_NEG2_RUNTIME_OBJECT_OFFSET: usize = 0x4cae;
const LOCOMOTIVE_POLICY_FIELD_NEG1_RUNTIME_OBJECT_OFFSET: usize = 0x4cb2;
const LOCOMOTIVE_POLICY_FIELD_0_RUNTIME_OBJECT_OFFSET: usize = 0x4c93;
const LOCOMOTIVE_POLICY_FIELD_1_RUNTIME_OBJECT_OFFSET: usize = 0x4c97;
const LOCOMOTIVE_POLICY_FIELD_2_RUNTIME_OBJECT_OFFSET: usize = 0x4c98;
const LOCOMOTIVE_POLICY_FIELD_3_RUNTIME_OBJECT_OFFSET: usize = 0x4c99;
const LOCOMOTIVE_POLICY_FIELD_4_RUNTIME_OBJECT_OFFSET: usize = 0x4cba;
const LOCOMOTIVE_POLICY_FIELD_5_RUNTIME_OBJECT_OFFSET: usize = 0x4cbe;
const PRE_RECIPE_SCALAR_PLATEAU_OFFSET: usize = 0x0fa7;
const PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET: usize = 0x0fe7;
const RECIPE_BOOK_ROOT_OFFSET: usize = 0x0fe7;
const RECIPE_BOOK_COUNT: usize = 12;
const RECIPE_BOOK_STRIDE: usize = 0x4e1;
const RECIPE_BOOK_HEAD_SAMPLE_LEN: usize = 16;
const RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET: usize = 0x3ed;
const RECIPE_BOOK_LINE_AREA_OFFSET: usize = 0x3f1;
const RECIPE_BOOK_LINE_COUNT: usize = 5;
const RECIPE_BOOK_LINE_STRIDE: usize = 0x30;
const RECIPE_BOOK_LINE_AREA_LEN: usize = RECIPE_BOOK_LINE_COUNT * RECIPE_BOOK_LINE_STRIDE;
const RECIPE_BOOK_SUMMARY_END_OFFSET: usize =
RECIPE_BOOK_ROOT_OFFSET + RECIPE_BOOK_COUNT * RECIPE_BOOK_STRIDE;
const RT3_SAVE_WORLD_BLOCK_CHUNK_TAG: u32 = 0x000032c8;
const RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG: u32 = 0x000032c9;
const RT3_SAVE_WORLD_BLOCK_LEN: usize = 0x4f2c;
const RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET: usize = 0x1d;
const RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET: usize = 0x21;
const RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_MULTIPLIER_RELATIVE_OFFSET: usize = 0x25;
const RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET: usize = 0x29;
const RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET: usize = 0x0d;
const RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_2_RELATIVE_OFFSET: usize = 0x11;
const RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_RELATIVE_OFFSET: usize = 0x15;
const RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_MIRROR_RELATIVE_OFFSET: usize = 0x19;
const RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_CANDIDATE_FIELDS: [(&str, usize); 11] = [
(
"current_calendar_tuple_word",
RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET,
),
(
"current_calendar_tuple_word_2",
RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_2_RELATIVE_OFFSET,
),
(
"absolute_calendar_counter",
RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_RELATIVE_OFFSET,
),
(
"absolute_calendar_counter_mirror",
RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_MIRROR_RELATIVE_OFFSET,
),
("selection_context_candidate_0", 0x1d),
("selection_context_candidate_1", 0x21),
("issue_0x37_multiplier", 0x25),
("issue_0x37_value", 0x29),
("issue_neighbor_candidate_0", 0x2d),
("issue_neighbor_candidate_1", 0x31),
("issue_neighbor_candidate_2", 0x35),
];
const RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_ROOT_RELATIVE_OFFSET: usize = 0x0d;
const RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_WINDOW_LEN_DWORDS: usize = 17;
const RT3_SAVE_WORLD_BLOCK_ISSUE_OPINION_BASE_TERMS_OFFSET: usize = 0x8a;
const RT3_SAVE_WORLD_BLOCK_ISSUE_OPINION_TERM_COUNT: usize = 0x3b;
const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_SELECTOR_RELATIVE_OFFSET: usize = 0x83;
const RT3_SAVE_WORLD_BLOCK_CAMPAIGN_OVERRIDE_FLAG_RELATIVE_OFFSET: usize = 0xc1;
const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_RELATIVE_OFFSET: usize = 0x0bbf;
const RT3_SAVE_WORLD_BLOCK_STOCK_POLICY_RELATIVE_OFFSET: usize = 0x4a83;
const RT3_SAVE_WORLD_BLOCK_BOND_POLICY_RELATIVE_OFFSET: usize = 0x4a87;
const RT3_SAVE_WORLD_BLOCK_BANKRUPTCY_POLICY_RELATIVE_OFFSET: usize = 0x4a8b;
const RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET: usize = 0x4a8f;
const RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET: usize = 0x4c78;
const RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET: usize = 0x0bda;
const RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_PRIMARY_RELATIVE_OFFSETS: [usize; 6] =
[0x0bde, 0x0be2, 0x0be6, 0x0bea, 0x0bee, 0x0bf2];
const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_COUNT: usize = 16;
const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_STRIDE: usize = 9;
const EVENT_RUNTIME_COLLECTION_METADATA_TAG: u16 = 0x4e99;
const EVENT_RUNTIME_COLLECTION_RECORDS_TAG: u16 = 0x4e9a;
const EVENT_RUNTIME_COLLECTION_CLOSE_TAG: u16 = 0x4e9b;
const EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION: u32 = 0x000003e9;
const INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT: usize = 19;
const INDEXED_COLLECTION_SERIALIZED_HEADER_LEN: usize =
INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT * 4;
const SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX: usize = 16;
const SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT: usize = 3;
const PACKED_EVENT_RECORDS_SYNTHETIC_MAGIC: &[u8; 8] = b"RPEVT001";
const PACKED_EVENT_RECORD_SYNTHETIC_MAGIC: &[u8; 4] = b"RPE1";
const PACKED_EVENT_RECORD_TEMPLATE_SYNTHETIC_MAGIC: &[u8; 4] = b"RPT1";
const PACKED_EVENT_REAL_CONDITION_MARKER: u16 = 0x526f;
const PACKED_EVENT_REAL_GROUPED_EFFECT_MARKER: u16 = 0x4eb8;
const PACKED_EVENT_REAL_CONDITION_ROW_LEN: usize = 0x1e;
const PACKED_EVENT_REAL_GROUPED_EFFECT_ROW_LEN: usize = 0x28;
const PACKED_EVENT_REAL_GROUP_COUNT: usize = 4;
const PACKED_EVENT_REAL_COMPACT_CONTROL_LEN: usize = 37;
const PACKED_EVENT_TEXT_BAND_LABELS: [&str; 6] = [
"primary_text_band",
"secondary_text_band_0",
"secondary_text_band_1",
"secondary_text_band_2",
"secondary_text_band_3",
"secondary_text_band_4",
];
const SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX: usize =
(POST_SPECIAL_CONDITIONS_SCALAR_OFFSET - SPECIAL_CONDITIONS_OFFSET) / 4;
const SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT: usize =
(SMP_ALIGNED_RUNTIME_RULE_END_OFFSET - POST_SPECIAL_CONDITIONS_SCALAR_OFFSET) / 4;
const SHARED_SIGNATURE_WORDS_1_TO_7: [u32; 7] = [
0x00002ee0, 0x00040001, 0x00028000, 0x00010000, 0x00000771, 0x00000771, 0x00000771,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct RealGroupedEffectDescriptorMetadata {
descriptor_id: u32,
label: &'static str,
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 CheckedInEventEffectsSemanticCatalogArtifact {
descriptors: Vec<CheckedInEventEffectSemanticRow>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
struct CheckedInEventEffectSemanticRow {
descriptor_id: u32,
label: String,
target_mask_bits: u8,
parameter_family: String,
runtime_key: Option<String>,
runtime_status: String,
executable_in_runtime: bool,
}
const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetadata; 12] = [
RealGroupedEffectDescriptorMetadata {
descriptor_id: 1,
label: "Player Cash",
target_mask_bits: 0x02,
parameter_family: "player_finance_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 2,
label: "Company Cash",
target_mask_bits: 0x01,
parameter_family: "company_finance_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 3,
label: "Territory - Allow All",
target_mask_bits: 0x05,
parameter_family: "territory_access_toggle",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 8,
label: "Economic Status",
target_mask_bits: 0x08,
parameter_family: "whole_game_state_enum",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 108,
label: "Use Wartime Cargos",
target_mask_bits: 0x08,
parameter_family: "special_condition_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 109,
label: "Turbo Diesel Availability",
target_mask_bits: 0x08,
parameter_family: "candidate_availability_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 110,
label: "Disable Stock Buying and Selling",
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 {
descriptor_id: 9,
label: "Confiscate All",
target_mask_bits: 0x01,
parameter_family: "company_confiscation_variant",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 13,
label: "Deactivate Company",
target_mask_bits: 0x01,
parameter_family: "company_lifecycle_toggle",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 14,
label: "Deactivate Player",
target_mask_bits: 0x02,
parameter_family: "player_lifecycle_toggle",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 15,
label: "Retire Train",
target_mask_bits: 0x0d,
parameter_family: "company_or_territory_asset_toggle",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 16,
label: "Company Track Pieces Buildable",
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: CheckedInEventEffectsSemanticCatalogArtifact = serde_json::from_str(
include_str!("../../../artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json"),
)
.expect("checked-in event-effects semantic catalog 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: CheckedInEventEffectSemanticRow,
) -> RealGroupedEffectDescriptorMetadata {
let label = Box::leak(row.label.clone().into_boxed_str()) as &'static str;
let parameter_family = Box::leak(row.parameter_family.into_boxed_str()) as &'static str;
let runtime_key = row
.runtime_key
.map(|key| Box::leak(key.into_boxed_str()) as &'static str);
let runtime_status = match row.runtime_status.as_str() {
"executable" => RealGroupedEffectRuntimeStatus::Executable,
"shell_owned" => RealGroupedEffectRuntimeStatus::ShellOwned,
"evidence_blocked" => RealGroupedEffectRuntimeStatus::EvidenceBlocked,
"variant_or_scope_blocked" => RealGroupedEffectRuntimeStatus::VariantOrScopeBlocked,
other => panic!("unknown checked-in event-effect runtime status {other}"),
};
RealGroupedEffectDescriptorMetadata {
descriptor_id: row.descriptor_id,
label,
target_mask_bits: row.target_mask_bits,
parameter_family,
runtime_key,
runtime_status,
executable_in_runtime: row.executable_in_runtime,
}
}
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 {
WorldVariable(u32),
Company(RuntimeCompanyMetric),
CompanyVariable(u32),
PlayerVariable(u32),
Chairman(RuntimeChairmanMetric),
Territory(RuntimeTerritoryMetric),
TerritoryVariable(u32),
CompanyTerritory(RuntimeTrackMetric),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RealWorldConditionKind {
SpecialCondition { label: &'static str },
CandidateAvailability,
NamedLocomotiveAvailability,
NamedLocomotiveCost,
CargoProductionSlot,
CargoProductionTotal,
FactoryProductionTotal,
FarmMineProductionTotal,
OtherCargoProductionTotal,
LimitedTrackBuildingAmount,
TerritoryAccessCost,
EconomicStatus,
WorldFlag { key: &'static str },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RealOrdinaryConditionKind {
Numeric(RealOrdinaryConditionMetric),
WorldState(RealWorldConditionKind),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct RealOrdinaryConditionMetadata {
raw_condition_id: i32,
label: &'static str,
kind: RealOrdinaryConditionKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct KnownCargoSlotDefinition {
slot_id: u32,
label: &'static str,
cargo_class: RuntimeCargoClass,
descriptor_id: u32,
}
const KNOWN_CARGO_SLOT_DEFINITIONS: [KnownCargoSlotDefinition; 11] = [
KnownCargoSlotDefinition {
slot_id: 1,
label: "Cargo Production Slot 1",
cargo_class: RuntimeCargoClass::Factory,
descriptor_id: 230,
},
KnownCargoSlotDefinition {
slot_id: 2,
label: "Cargo Production Slot 2",
cargo_class: RuntimeCargoClass::Factory,
descriptor_id: 231,
},
KnownCargoSlotDefinition {
slot_id: 3,
label: "Cargo Production Slot 3",
cargo_class: RuntimeCargoClass::Factory,
descriptor_id: 232,
},
KnownCargoSlotDefinition {
slot_id: 4,
label: "Cargo Production Slot 4",
cargo_class: RuntimeCargoClass::Factory,
descriptor_id: 233,
},
KnownCargoSlotDefinition {
slot_id: 5,
label: "Cargo Production Slot 5",
cargo_class: RuntimeCargoClass::FarmMine,
descriptor_id: 234,
},
KnownCargoSlotDefinition {
slot_id: 6,
label: "Cargo Production Slot 6",
cargo_class: RuntimeCargoClass::FarmMine,
descriptor_id: 235,
},
KnownCargoSlotDefinition {
slot_id: 7,
label: "Cargo Production Slot 7",
cargo_class: RuntimeCargoClass::FarmMine,
descriptor_id: 236,
},
KnownCargoSlotDefinition {
slot_id: 8,
label: "Cargo Production Slot 8",
cargo_class: RuntimeCargoClass::FarmMine,
descriptor_id: 237,
},
KnownCargoSlotDefinition {
slot_id: 9,
label: "Cargo Production Slot 9",
cargo_class: RuntimeCargoClass::Other,
descriptor_id: 238,
},
KnownCargoSlotDefinition {
slot_id: 10,
label: "Cargo Production Slot 10",
cargo_class: RuntimeCargoClass::Other,
descriptor_id: 239,
},
KnownCargoSlotDefinition {
slot_id: 11,
label: "Cargo Production Slot 11",
cargo_class: RuntimeCargoClass::Other,
descriptor_id: 240,
},
];
const GROUNDED_LOCOMOTIVE_PREFIX: [&str; 61] = [
"2-D-2",
"E-88",
"Adler 2-2-2",
"USA 103",
"American 4-4-0",
"Atlantic 4-4-2",
"Baldwin 0-6-0",
"Be 5/7",
"Beuth 2-2-2",
"Big Boy 4-8-8-4",
"C55 Deltic",
"Camelback 0-6-0",
"Challenger 4-6-6-4",
"Class 01 4-6-2",
"Class 103",
"Class 132",
"Class 500 4-6-0",
"Class 9100",
"Class EF 66",
"Class 6E",
"Consolidation 2-8-0",
"Crampton 4-2-0",
"DD 080-X",
"DD40AX",
"Duke Class 4-4-0",
"E18",
"E428",
"Brenner E412",
"E60CP",
"Eight Wheeler 4-4-0",
"EP-2 Bipolar",
"ET22",
"F3",
"Fairlie 0-6-6-0",
"Firefly 2-2-2",
"FP45",
"Ge 6/6 Crocodile",
"GG1",
"GP7",
"H10 2-8-2",
"HST 125",
"Kriegslok 2-10-0",
"Mallard 4-6-2",
"Norris 4-2-0",
"Northern 4-8-4",
"Orca NX462",
"Pacific 4-6-2",
"Planet 2-2-0",
"Re 6/6",
"Red Devil 4-8-4",
"S3 4-4-0",
"NA-90D",
"Shay (2-Truck)",
"Shinkansen Series 0",
"Stirling 4-2-2",
"Trans-Euro",
"V200",
"VL80T",
"GP 35",
"U1",
"Zephyr",
];
const REAL_CANDIDATE_AVAILABILITY_CONDITION_TEMPLATE_ID: i32 = 435;
const REAL_WORLD_VARIABLE_1_CONDITION_ID: i32 = 2241;
const REAL_WORLD_VARIABLE_2_CONDITION_ID: i32 = 2242;
const REAL_WORLD_VARIABLE_3_CONDITION_ID: i32 = 2243;
const REAL_WORLD_VARIABLE_4_CONDITION_ID: i32 = 2244;
const REAL_COMPANY_VARIABLE_1_CONDITION_ID: i32 = 2245;
const REAL_COMPANY_VARIABLE_2_CONDITION_ID: i32 = 2246;
const REAL_COMPANY_VARIABLE_3_CONDITION_ID: i32 = 2247;
const REAL_COMPANY_VARIABLE_4_CONDITION_ID: i32 = 2248;
const REAL_PLAYER_VARIABLE_1_CONDITION_ID: i32 = 2249;
const REAL_PLAYER_VARIABLE_2_CONDITION_ID: i32 = 2250;
const REAL_PLAYER_VARIABLE_3_CONDITION_ID: i32 = 2251;
const REAL_PLAYER_VARIABLE_4_CONDITION_ID: i32 = 2252;
const REAL_TERRITORY_VARIABLE_1_CONDITION_ID: i32 = 2253;
const REAL_TERRITORY_VARIABLE_2_CONDITION_ID: i32 = 2254;
const REAL_TERRITORY_VARIABLE_3_CONDITION_ID: i32 = 2255;
const REAL_TERRITORY_VARIABLE_4_CONDITION_ID: i32 = 2256;
const REAL_CHAIRMAN_CASH_CONDITION_ID: i32 = 2218;
const REAL_CHAIRMAN_HOLDINGS_TOTAL_CONDITION_ID: i32 = 2239;
const REAL_CHAIRMAN_NET_WORTH_CONDITION_ID: i32 = 2240;
const REAL_CHAIRMAN_PURCHASING_POWER_CONDITION_ID: i32 = 1247;
const REAL_INVESTOR_CONFIDENCE_CONDITION_ID: i32 = 2366;
const REAL_CREDIT_RATING_CONDITION_ID: i32 = 2367;
const REAL_PRIME_RATE_CONDITION_ID: i32 = 2368;
const REAL_MANAGEMENT_ATTITUDE_CONDITION_ID: i32 = 2369;
const REAL_BOOK_VALUE_PER_SHARE_CONDITION_ID: i32 = 2620;
const REAL_CARGO_PRODUCTION_CONDITION_TEMPLATE_ID: i32 = 200;
const REAL_NAMED_LOCOMOTIVE_AVAILABILITY_CONDITION_ID: i32 = 2422;
const REAL_NAMED_LOCOMOTIVE_COST_CONDITION_ID: i32 = 2423;
const REAL_CARGO_PRODUCTION_TOTAL_CONDITION_ID: i32 = 2418;
const REAL_FACTORY_PRODUCTION_TOTAL_CONDITION_ID: i32 = 2419;
const REAL_FARM_MINE_PRODUCTION_TOTAL_CONDITION_ID: i32 = 2420;
const REAL_OTHER_CARGO_PRODUCTION_TOTAL_CONDITION_ID: i32 = 2421;
const REAL_LIMITED_TRACK_BUILDING_AMOUNT_CONDITION_ID: i32 = 2547;
const REAL_TERRITORY_ACCESS_COST_CONDITION_ID: i32 = 1516;
const REAL_ORDINARY_CONDITION_METADATA: [RealOrdinaryConditionMetadata; 56] = [
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_WORLD_VARIABLE_1_CONDITION_ID,
label: "Game Variable 1",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(1)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_WORLD_VARIABLE_2_CONDITION_ID,
label: "Game Variable 2",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(2)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_WORLD_VARIABLE_3_CONDITION_ID,
label: "Game Variable 3",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(3)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_WORLD_VARIABLE_4_CONDITION_ID,
label: "Game Variable 4",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(4)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_COMPANY_VARIABLE_1_CONDITION_ID,
label: "Company Variable 1",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(1)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_COMPANY_VARIABLE_2_CONDITION_ID,
label: "Company Variable 2",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(2)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_COMPANY_VARIABLE_3_CONDITION_ID,
label: "Company Variable 3",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(3)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_COMPANY_VARIABLE_4_CONDITION_ID,
label: "Company Variable 4",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(4)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_PLAYER_VARIABLE_1_CONDITION_ID,
label: "Player Variable 1",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(1)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_PLAYER_VARIABLE_2_CONDITION_ID,
label: "Player Variable 2",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(2)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_PLAYER_VARIABLE_3_CONDITION_ID,
label: "Player Variable 3",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(3)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_PLAYER_VARIABLE_4_CONDITION_ID,
label: "Player Variable 4",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(4)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_TERRITORY_VARIABLE_1_CONDITION_ID,
label: "Territory Variable 1",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(1)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_TERRITORY_VARIABLE_2_CONDITION_ID,
label: "Territory Variable 2",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(2)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_TERRITORY_VARIABLE_3_CONDITION_ID,
label: "Territory Variable 3",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(3)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_TERRITORY_VARIABLE_4_CONDITION_ID,
label: "Territory Variable 4",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(4)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 1802,
label: "Current Cash",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::CurrentCash,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_CHAIRMAN_CASH_CONDITION_ID,
label: "Player Cash",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(
RuntimeChairmanMetric::CurrentCash,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_CHAIRMAN_HOLDINGS_TOTAL_CONDITION_ID,
label: "Player Stock Value",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(
RuntimeChairmanMetric::HoldingsValueTotal,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_CHAIRMAN_NET_WORTH_CONDITION_ID,
label: "Player Net Worth",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(
RuntimeChairmanMetric::NetWorthTotal,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_CHAIRMAN_PURCHASING_POWER_CONDITION_ID,
label: "Purchasing Power",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(
RuntimeChairmanMetric::PurchasingPowerTotal,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 951,
label: "Total Debt",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::TotalDebt,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_INVESTOR_CONFIDENCE_CONDITION_ID,
label: "Investor Confidence",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::InvestorConfidence,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_CREDIT_RATING_CONDITION_ID,
label: "Credit Rating",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::CreditRating,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_PRIME_RATE_CONDITION_ID,
label: "Prime Rate",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::PrimeRate,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_MANAGEMENT_ATTITUDE_CONDITION_ID,
label: "Management Attitude",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::ManagementAttitude,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_BOOK_VALUE_PER_SHARE_CONDITION_ID,
label: "Book Value Per Share",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::BookValuePerShare,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2293,
label: "Company Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::TrackPiecesTotal,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2294,
label: "Company Single Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::TrackPiecesSingle,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2295,
label: "Company Double Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::TrackPiecesDouble,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2296,
label: "Company Transition Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::TrackPiecesTransition,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2297,
label: "Company Electric Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::TrackPiecesElectric,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2298,
label: "Company Non-Electric Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
RuntimeCompanyMetric::TrackPiecesNonElectric,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2313,
label: "Territory Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
RuntimeTerritoryMetric::TrackPiecesTotal,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2314,
label: "Territory Single Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
RuntimeTerritoryMetric::TrackPiecesSingle,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2315,
label: "Territory Double Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
RuntimeTerritoryMetric::TrackPiecesDouble,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2316,
label: "Territory Transition Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
RuntimeTerritoryMetric::TrackPiecesTransition,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2317,
label: "Territory Electric Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
RuntimeTerritoryMetric::TrackPiecesElectric,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2318,
label: "Territory Non-Electric Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
RuntimeTerritoryMetric::TrackPiecesNonElectric,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2323,
label: "Company-Territory Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
RuntimeTrackMetric::Total,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2324,
label: "Company-Territory Single Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
RuntimeTrackMetric::Single,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2325,
label: "Company-Territory Double Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
RuntimeTrackMetric::Double,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2326,
label: "Company-Territory Transition Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
RuntimeTrackMetric::Transition,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2327,
label: "Company-Territory Electric Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
RuntimeTrackMetric::Electric,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2328,
label: "Company-Territory Non-Electric Track Pieces",
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
RuntimeTrackMetric::NonElectric,
)),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_CANDIDATE_AVAILABILITY_CONDITION_TEMPLATE_ID,
label: "%1 Avail.",
kind: RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CandidateAvailability),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_CARGO_PRODUCTION_CONDITION_TEMPLATE_ID,
label: "%1 Production",
kind: RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CargoProductionSlot),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_NAMED_LOCOMOTIVE_AVAILABILITY_CONDITION_ID,
label: "Unknown Loco Available",
kind: RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::NamedLocomotiveAvailability,
),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_NAMED_LOCOMOTIVE_COST_CONDITION_ID,
label: "Unknown Loco Cost",
kind: RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::NamedLocomotiveCost),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_CARGO_PRODUCTION_TOTAL_CONDITION_ID,
label: "All Cargo Production",
kind: RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CargoProductionTotal),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_FACTORY_PRODUCTION_TOTAL_CONDITION_ID,
label: "All Factory Production",
kind: RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::FactoryProductionTotal),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_FARM_MINE_PRODUCTION_TOTAL_CONDITION_ID,
label: "All Farm/Mine Production",
kind: RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::FarmMineProductionTotal,
),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_OTHER_CARGO_PRODUCTION_TOTAL_CONDITION_ID,
label: "Unknown Cargo Production",
kind: RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::OtherCargoProductionTotal,
),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_LIMITED_TRACK_BUILDING_AMOUNT_CONDITION_ID,
label: "Limited Track Building Amount",
kind: RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::LimitedTrackBuildingAmount,
),
},
RealOrdinaryConditionMetadata {
raw_condition_id: REAL_TERRITORY_ACCESS_COST_CONDITION_ID,
label: "Access Rights Cost:",
kind: RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::TerritoryAccessCost),
},
RealOrdinaryConditionMetadata {
raw_condition_id: 2350,
label: "Economic Status",
kind: RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::EconomicStatus),
},
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct KnownSpecialConditionDefinition {
slot_index: u8,
hidden: bool,
label_id: u32,
help_id: u32,
label: &'static str,
}
const KNOWN_SPECIAL_CONDITION_DEFINITIONS: [KnownSpecialConditionDefinition;
SPECIAL_CONDITION_COUNT] = [
KnownSpecialConditionDefinition {
slot_index: 0,
hidden: false,
label_id: 2535,
help_id: 2564,
label: "Disable Stock Buying and Selling",
},
KnownSpecialConditionDefinition {
slot_index: 1,
hidden: false,
label_id: 2536,
help_id: 2565,
label: "Disable Margin Buying/Short Selling Stock",
},
KnownSpecialConditionDefinition {
slot_index: 2,
hidden: false,
label_id: 2537,
help_id: 2566,
label: "Disable Company Issue/Buy Back Stock",
},
KnownSpecialConditionDefinition {
slot_index: 3,
hidden: false,
label_id: 2538,
help_id: 2567,
label: "Disable Issuing/Repaying Bonds",
},
KnownSpecialConditionDefinition {
slot_index: 4,
hidden: false,
label_id: 2539,
help_id: 2568,
label: "Disable Declaring Bankruptcy",
},
KnownSpecialConditionDefinition {
slot_index: 5,
hidden: false,
label_id: 2540,
help_id: 2569,
label: "Disable Changing the Dividend Rate",
},
KnownSpecialConditionDefinition {
slot_index: 6,
hidden: false,
label_id: 2541,
help_id: 2570,
label: "Disable Replacing a Locomotive",
},
KnownSpecialConditionDefinition {
slot_index: 7,
hidden: false,
label_id: 2542,
help_id: 2571,
label: "Disable Retiring a Train",
},
KnownSpecialConditionDefinition {
slot_index: 8,
hidden: false,
label_id: 2543,
help_id: 2572,
label: "Disable Changing Cargo Consist On Train",
},
KnownSpecialConditionDefinition {
slot_index: 9,
hidden: false,
label_id: 2544,
help_id: 2573,
label: "Disable Buying a Train",
},
KnownSpecialConditionDefinition {
slot_index: 10,
hidden: false,
label_id: 2545,
help_id: 2574,
label: "Disable All Track Building",
},
KnownSpecialConditionDefinition {
slot_index: 11,
hidden: false,
label_id: 2546,
help_id: 2575,
label: "Disable Unconnected Track Building",
},
KnownSpecialConditionDefinition {
slot_index: 12,
hidden: false,
label_id: 2547,
help_id: 2576,
label: "Limited Track Building Amount",
},
KnownSpecialConditionDefinition {
slot_index: 13,
hidden: false,
label_id: 2548,
help_id: 2577,
label: "Disable Building Stations",
},
KnownSpecialConditionDefinition {
slot_index: 14,
hidden: false,
label_id: 2549,
help_id: 2578,
label: "Disable Building Hotel/Restaurant/Tavern/Post Office",
},
KnownSpecialConditionDefinition {
slot_index: 15,
hidden: false,
label_id: 2550,
help_id: 2579,
label: "Disable Building Customs House",
},
KnownSpecialConditionDefinition {
slot_index: 16,
hidden: false,
label_id: 2551,
help_id: 2580,
label: "Disable Building Industry Buildings",
},
KnownSpecialConditionDefinition {
slot_index: 17,
hidden: false,
label_id: 2552,
help_id: 2581,
label: "Disable Buying Existing Industry Buildings",
},
KnownSpecialConditionDefinition {
slot_index: 18,
hidden: false,
label_id: 2553,
help_id: 2582,
label: "Disable Being Fired As Chairman",
},
KnownSpecialConditionDefinition {
slot_index: 19,
hidden: false,
label_id: 2554,
help_id: 2583,
label: "Disable Resigning as Chairman",
},
KnownSpecialConditionDefinition {
slot_index: 20,
hidden: false,
label_id: 2555,
help_id: 2584,
label: "Disable Chairmanship Takeover",
},
KnownSpecialConditionDefinition {
slot_index: 21,
hidden: false,
label_id: 2556,
help_id: 2585,
label: "Disable Starting Any Companies",
},
KnownSpecialConditionDefinition {
slot_index: 22,
hidden: false,
label_id: 2557,
help_id: 2586,
label: "Disable Starting Multiple Companies",
},
KnownSpecialConditionDefinition {
slot_index: 23,
hidden: false,
label_id: 2558,
help_id: 2587,
label: "Disable Merging Companies",
},
KnownSpecialConditionDefinition {
slot_index: 24,
hidden: false,
label_id: 2559,
help_id: 2588,
label: "Disable Bulldozing",
},
KnownSpecialConditionDefinition {
slot_index: 25,
hidden: false,
label_id: 2560,
help_id: 2589,
label: "Show Visited Track",
},
KnownSpecialConditionDefinition {
slot_index: 26,
hidden: false,
label_id: 2561,
help_id: 2590,
label: "Show Visited Stations",
},
KnownSpecialConditionDefinition {
slot_index: 27,
hidden: false,
label_id: 2562,
help_id: 2591,
label: "Use Slow Date",
},
KnownSpecialConditionDefinition {
slot_index: 28,
hidden: false,
label_id: 2563,
help_id: 2592,
label: "Completely Disable Money-Related Things",
},
KnownSpecialConditionDefinition {
slot_index: 29,
hidden: false,
label_id: 2874,
help_id: 2875,
label: "Use Bio-Accelerator Cars",
},
KnownSpecialConditionDefinition {
slot_index: 30,
hidden: false,
label_id: 3722,
help_id: 3723,
label: "Disable Cargo Economy",
},
KnownSpecialConditionDefinition {
slot_index: 31,
hidden: false,
label_id: 3835,
help_id: 3836,
label: "Use Wartime Cargos",
},
KnownSpecialConditionDefinition {
slot_index: 32,
hidden: false,
label_id: 3850,
help_id: 3851,
label: "Disable Train Crashes",
},
KnownSpecialConditionDefinition {
slot_index: 33,
hidden: false,
label_id: 3852,
help_id: 3853,
label: "Disable Train Crashes AND Breakdowns",
},
KnownSpecialConditionDefinition {
slot_index: 34,
hidden: false,
label_id: 3920,
help_id: 3921,
label: "AI Ignore Territories At Startup",
},
KnownSpecialConditionDefinition {
slot_index: 35,
hidden: true,
label_id: 3,
help_id: 3,
label: "Hidden sentinel",
},
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct KnownTagDefinition {
tag_id: u16,
label: &'static str,
grounded_meaning: &'static str,
}
const KNOWN_TAG_DEFINITIONS: [KnownTagDefinition; 4] = [
KnownTagDefinition {
tag_id: 0x2cee,
label: "overlay_mask_plane_primary",
grounded_meaning: "Primary one-byte overlay mask plane restored into world offset +0x1655.",
},
KnownTagDefinition {
tag_id: 0x2d51,
label: "overlay_mask_plane_secondary",
grounded_meaning: "Secondary one-byte overlay mask plane restored into world offset +0x1659.",
},
KnownTagDefinition {
tag_id: 0x9471,
label: "sidecar_byte_plane_family_low",
grounded_meaning: "Lower bound of the grounded sidecar byte-plane chunk family.",
},
KnownTagDefinition {
tag_id: 0x9472,
label: "sidecar_byte_plane_family_high",
grounded_meaning: "Upper bound of the grounded sidecar byte-plane chunk family.",
},
];
fn known_special_condition_definition_for_label_id(
label_id: u32,
) -> Option<KnownSpecialConditionDefinition> {
KNOWN_SPECIAL_CONDITION_DEFINITIONS
.iter()
.copied()
.find(|definition| !definition.hidden && definition.label_id == label_id)
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpKnownTagHit {
pub tag_id: u16,
pub tag_hex: String,
pub label: String,
pub grounded_meaning: String,
pub hit_count: usize,
pub sample_offsets: Vec<usize>,
pub last_offset: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpPreambleWord {
pub index: usize,
pub offset: usize,
pub value_le: u32,
pub value_hex: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpPreamble {
pub byte_len: usize,
pub word_count: usize,
pub words: Vec<SmpPreambleWord>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpAsciiPreview {
pub offset: usize,
pub byte_len: usize,
pub preview: String,
pub truncated: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpSharedHeader {
pub byte_len: usize,
pub root_kind_word: u32,
pub root_kind_word_hex: String,
pub primary_family_tag: u32,
pub primary_family_tag_hex: String,
pub shared_signature_words_1_to_7: Vec<u32>,
pub shared_signature_hex_words_1_to_7: Vec<String>,
pub matches_grounded_common_signature: bool,
pub payload_window_words_8_to_9: Vec<u32>,
pub payload_window_hex_words_8_to_9: Vec<String>,
pub reserved_words_10_to_14: Vec<u32>,
pub reserved_words_10_to_14_all_zero: bool,
pub final_flag_word: u32,
pub final_flag_word_hex: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpHeaderVariantProbe {
pub variant_family: String,
pub variant_evidence: Vec<String>,
pub is_known_family: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpEarlyContentProbe {
pub first_post_text_nonzero_offset: usize,
pub zero_pad_after_text_len: usize,
pub first_post_text_block_len: usize,
pub first_post_text_block_hex: String,
pub trailing_zero_pad_after_first_block_len: usize,
pub secondary_nonzero_offset: Option<usize>,
pub secondary_aligned_word_window_offset: Option<usize>,
pub secondary_aligned_word_window_words: Vec<u32>,
pub secondary_aligned_word_window_hex_words: Vec<String>,
pub secondary_preview_hex: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpSecondaryVariantProbe {
pub aligned_window_offset: usize,
pub words: Vec<u32>,
pub hex_words: Vec<String>,
pub variant_family: String,
pub variant_evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpContainerProfile {
pub profile_family: String,
pub profile_evidence: Vec<String>,
pub is_known_profile: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpSaveBootstrapBlock {
pub profile_family: String,
pub aligned_window_offset: usize,
pub leading_word: u32,
pub leading_word_hex: String,
pub anchor_word: u32,
pub anchor_word_hex: String,
pub descriptor_word_2: u32,
pub descriptor_word_2_hex: String,
pub descriptor_word_3: u32,
pub descriptor_word_3_hex: String,
pub descriptor_word_4: u32,
pub descriptor_word_4_hex: String,
pub descriptor_word_5: u32,
pub descriptor_word_5_hex: String,
pub descriptor_word_6: u32,
pub descriptor_word_6_hex: String,
pub descriptor_word_7: u32,
pub descriptor_word_7_hex: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpSaveAnchorRunBlock {
pub profile_family: String,
pub cycle_start_offset: usize,
pub cycle_words: Vec<u32>,
pub cycle_hex_words: Vec<String>,
pub full_cycle_count: usize,
pub partial_cycle_word_count: usize,
pub trailer_offset: usize,
pub trailer_words: Vec<u32>,
pub trailer_hex_words: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpRuntimeAnchorCycleBlock {
pub profile_family: String,
pub cycle_start_offset: usize,
pub cycle_words: Vec<u32>,
pub cycle_hex_words: Vec<String>,
pub full_cycle_count: usize,
pub partial_cycle_word_count: usize,
pub trailer_offset: usize,
pub trailer_words: Vec<u32>,
pub trailer_hex_words: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpRuntimeTrailerBlock {
pub profile_family: String,
pub trailer_family: String,
pub trailer_evidence: Vec<String>,
pub trailer_offset: usize,
pub prefix_words_0_to_5: Vec<u32>,
pub prefix_hex_words_0_to_5: Vec<String>,
pub tag_word_6: u32,
pub tag_word_6_hex: String,
pub tag_chunk_id_u16: u16,
pub tag_chunk_id_hex: String,
pub tag_chunk_id_grounded_alignment: Option<String>,
pub length_word_7: u32,
pub length_word_7_hex: String,
pub length_high_u16: u16,
pub length_high_hex: String,
pub selector_word_8: u32,
pub selector_word_8_hex: String,
pub selector_high_u16: u16,
pub selector_high_hex: String,
pub layout_word_9: u32,
pub layout_word_9_hex: String,
pub descriptor_word_10: u32,
pub descriptor_word_10_hex: String,
pub descriptor_high_u16: u16,
pub descriptor_high_hex: String,
pub descriptor_word_11: u32,
pub descriptor_word_11_hex: String,
pub counter_word_12: u32,
pub counter_word_12_hex: String,
pub offset_word_13: u32,
pub offset_word_13_hex: String,
pub span_word_14: u32,
pub span_word_14_hex: String,
pub mode_word_15: u32,
pub mode_word_15_hex: String,
pub words: Vec<u32>,
pub hex_words: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpRuntimePostSpanProbe {
pub profile_family: String,
pub span_target_offset: usize,
pub next_nonzero_offset: Option<usize>,
pub next_aligned_candidate_offset: Option<usize>,
pub next_aligned_candidate_words: Vec<u32>,
pub next_aligned_candidate_hex_words: Vec<String>,
pub header_candidates: Vec<SmpRuntimePostSpanHeaderCandidate>,
pub grounded_progress_hits: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpRuntimePostSpanHeaderCandidate {
pub offset: usize,
pub words: Vec<u32>,
pub hex_words: Vec<String>,
pub dense_word_count: usize,
pub high_u16_words: Vec<u16>,
pub high_hex_words: Vec<String>,
pub grounded_alignments: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpRt3105PostSpanBridgeProbe {
pub profile_family: String,
pub bridge_family: String,
pub bridge_evidence: Vec<String>,
pub span_target_offset: usize,
pub next_candidate_offset: Option<usize>,
pub next_candidate_delta_from_span_target: Option<usize>,
pub packed_profile_offset: usize,
pub packed_profile_delta_from_span_target: usize,
pub next_candidate_delta_from_packed_profile: Option<i64>,
pub selector_high_u16: u16,
pub selector_high_hex: String,
pub descriptor_high_u16: u16,
pub descriptor_high_hex: String,
pub next_candidate_high_u16_words: Vec<u16>,
pub next_candidate_high_hex_words: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpRt3105SaveBridgePayloadProbe {
pub profile_family: String,
pub bridge_family: String,
pub primary_block_offset: usize,
pub primary_block_len: usize,
pub primary_block_len_hex: String,
pub primary_words: Vec<u32>,
pub primary_hex_words: Vec<String>,
pub secondary_block_offset: usize,
pub secondary_block_delta_from_primary: usize,
pub secondary_block_delta_from_primary_hex: String,
pub secondary_block_end_offset: usize,
pub secondary_block_len: usize,
pub secondary_block_len_hex: String,
pub secondary_preview_word_count: usize,
pub secondary_words: Vec<u32>,
pub secondary_hex_words: Vec<String>,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpSaveWorldSelectionContextProbe {
pub profile_family: String,
pub source_kind: String,
pub semantic_family: String,
pub chunk_tag_offset: usize,
pub payload_offset: usize,
pub payload_len: usize,
pub payload_len_hex: String,
pub selected_company_id_offset: usize,
pub selected_company_id: u32,
pub selected_company_id_hex: String,
pub selected_chairman_profile_id_offset: usize,
pub selected_chairman_profile_id: u32,
pub selected_chairman_profile_id_hex: String,
pub chairman_slot_selector_offset: usize,
pub chairman_slot_selectors: Vec<u8>,
pub campaign_override_flag_offset: usize,
pub campaign_override_flag: u8,
pub campaign_override_flag_hex: String,
pub chairman_role_gate_offset: usize,
pub chairman_role_gate_bytes: Vec<u8>,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SmpSaveWorldEconomicTuningProbe {
pub profile_family: String,
pub source_kind: String,
pub semantic_family: String,
pub chunk_tag_offset: usize,
pub payload_offset: usize,
pub payload_len: usize,
pub payload_len_hex: String,
pub mirror_lane: SmpSaveDwordCandidate,
pub tuning_lanes: Vec<SmpSaveDwordCandidate>,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SmpSaveWorldIssue37Probe {
pub profile_family: String,
pub source_kind: String,
pub semantic_family: String,
pub chunk_tag_offset: usize,
pub payload_offset: usize,
pub payload_len: usize,
pub payload_len_hex: String,
pub issue_37_raw_u8: u8,
pub issue_37_raw_hex: String,
pub issue_38_raw_u8: u8,
pub issue_38_raw_hex: String,
pub issue_39_raw_u8: u8,
pub issue_39_raw_hex: String,
pub issue_3a_raw_u8: u8,
pub issue_3a_raw_hex: String,
pub issue_value_lane: SmpSaveDwordCandidate,
pub multiplier_lane: SmpSaveDwordCandidate,
#[serde(default)]
pub issue_opinion_base_terms_raw_i32: Vec<i32>,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SmpSaveWorldFinanceNeighborhoodProbe {
pub profile_family: String,
pub source_kind: String,
pub semantic_family: String,
pub chunk_tag_offset: usize,
pub payload_offset: usize,
pub payload_len: usize,
pub payload_len_hex: String,
pub packed_year_word_raw_u16: u16,
pub packed_year_word_raw_hex: String,
pub partial_year_progress_raw_u8: u8,
pub partial_year_progress_raw_hex: String,
pub current_calendar_tuple_word_lane: SmpSaveDwordCandidate,
pub current_calendar_tuple_word_2_lane: SmpSaveDwordCandidate,
pub absolute_counter_lane: SmpSaveDwordCandidate,
pub absolute_counter_mirror_lane: SmpSaveDwordCandidate,
pub stock_policy_raw_u8: u8,
pub stock_policy_raw_hex: String,
pub bond_policy_raw_u8: u8,
pub bond_policy_raw_hex: String,
pub bankruptcy_policy_raw_u8: u8,
pub bankruptcy_policy_raw_hex: String,
pub dividend_policy_raw_u8: u8,
pub dividend_policy_raw_hex: String,
pub building_density_growth_setting_lane: SmpSaveDwordCandidate,
pub dword_candidates: Vec<SmpSaveDwordCandidate>,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpSaveTaggedCollectionHeaderProbe {
pub profile_family: String,
pub source_kind: String,
pub semantic_family: String,
pub metadata_tag_offset: usize,
pub records_tag_offset: usize,
pub close_tag_offset: usize,
pub direct_collection_flag: u32,
pub direct_collection_flag_hex: String,
pub direct_record_stride: u32,
pub direct_record_stride_hex: String,
pub live_id_bound: u32,
pub live_id_bound_hex: String,
pub live_record_count: u32,
pub live_record_count_hex: String,
pub header_words: Vec<u32>,
pub header_hex_words: Vec<String>,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpSaveRegionCollectionDirectoryEntryProbe {
pub live_entry_id: u32,
pub payload_relative_offset: u32,
pub payload_relative_offset_hex: String,
pub payload_absolute_offset: usize,
pub previous_live_entry_id: u32,
pub previous_live_entry_id_hex: String,
pub next_live_entry_id: u32,
pub next_live_entry_id_hex: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpSaveRegionCollectionDirectoryProbe {
pub profile_family: String,
pub source_kind: String,
pub semantic_family: String,
pub metadata_tag_offset: usize,
pub records_tag_offset: usize,
pub close_tag_offset: usize,
pub directory_root_dword_index: usize,
pub directory_entry_dword_count: usize,
pub live_record_count: u32,
pub live_id_bound: u32,
#[serde(default)]
pub chain_head_live_entry_id: Option<u32>,
#[serde(default)]
pub chain_tail_live_entry_id: Option<u32>,
pub entries: Vec<SmpSaveRegionCollectionDirectoryEntryProbe>,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpRt3105SaveNameTableProbe {
pub profile_family: String,
pub source_kind: String,
pub semantic_family: String,
pub semantic_alignment: Vec<String>,
pub header_offset: usize,
pub header_word_0: u32,
pub header_word_0_hex: String,
pub header_word_1: u32,
pub header_word_1_hex: String,
pub header_word_2: u32,
pub header_word_2_hex: String,
pub entry_stride: usize,
pub entry_stride_hex: String,
pub header_prefix_word_count: usize,
pub observed_entry_capacity: usize,
pub observed_entry_count: usize,
pub zero_trailer_entry_count: usize,
pub nonzero_trailer_entry_count: usize,
pub distinct_trailer_words: Vec<u32>,
pub distinct_trailer_hex_words: Vec<String>,
pub zero_trailer_entry_names: Vec<String>,
pub entries_offset: usize,
pub entries_end_offset: usize,
pub trailing_footer_hex: String,
pub footer_progress_word_0: u32,
pub footer_progress_word_0_hex: String,
pub footer_progress_word_1: u32,
pub footer_progress_word_1_hex: String,
pub footer_trailing_byte: u8,
pub footer_trailing_byte_hex: String,
pub footer_grounded_alignments: Vec<String>,
pub entries: Vec<SmpRt3105SaveNameTableEntry>,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpRt3105SaveNamedLocomotiveAvailabilityProbe {
pub profile_family: String,
pub source_kind: String,
pub semantic_family: String,
pub semantic_alignment: Vec<String>,
pub entries_offset: usize,
pub entry_stride: usize,
pub entry_stride_hex: String,
pub observed_entry_count: usize,
pub zero_availability_count: usize,
pub zero_availability_names: Vec<String>,
pub entries_end_offset: usize,
pub entries: Vec<SmpRt3105SaveNameTableEntry>,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpRt3105SaveNameTableEntry {
pub index: usize,
pub offset: usize,
pub text: String,
pub availability_dword: u32,
pub availability_dword_hex: String,
pub trailer_word: u32,
pub trailer_word_hex: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpSpecialConditionEntry {
pub slot_index: u8,
pub hidden: bool,
pub label_id: u32,
pub help_id: u32,
pub label: String,
pub value: u32,
pub value_hex: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpSpecialConditionsProbe {
pub profile_family: String,
pub source_kind: String,
pub table_offset: usize,
pub table_len: usize,
pub enabled_visible_count: usize,
pub enabled_visible_labels: Vec<String>,
pub hidden_sentinel_slot_index: u8,
pub hidden_sentinel_value: u32,
pub hidden_sentinel_value_hex: String,
pub entries: Vec<SmpSpecialConditionEntry>,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpAlignedRuntimeRuleBandLane {
pub band_index: usize,
pub absolute_offset: usize,
pub relative_offset: usize,
pub absolute_offset_hex: String,
pub relative_offset_hex: String,
pub lane_kind: String,
pub known_label: Option<String>,
pub value: u32,
pub value_hex: String,
pub probable_f32_le: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpAlignedRuntimeRuleBandProbe {
pub profile_family: String,
pub source_kind: String,
pub band_offset: usize,
pub band_end_offset: usize,
pub band_len: usize,
pub band_len_hex: String,
pub dword_count: usize,
pub known_editor_rule_dword_count: usize,
pub trailing_scalar_index: usize,
pub trailing_scalar_offset: usize,
pub trailing_scalar_offset_hex: String,
pub post_window_overlap_start_index: usize,
pub post_window_overlap_dword_count: usize,
pub post_window_overlap_end_index: usize,
pub post_window_overlap_post_relative_offset_start_hex: String,
pub post_window_overlap_post_relative_offset_end_hex: String,
pub nonzero_post_window_overlap_band_indices: Vec<usize>,
pub nonzero_post_window_overlap_post_relative_offset_hexes: Vec<String>,
pub nonzero_lane_count: usize,
pub nonzero_band_indices: Vec<usize>,
pub nonzero_relative_offset_hexes: Vec<String>,
pub nonzero_lanes: Vec<SmpAlignedRuntimeRuleBandLane>,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpPostSpecialConditionsScalarLane {
pub absolute_offset: usize,
pub relative_offset: usize,
pub absolute_offset_hex: String,
pub relative_offset_hex: String,
pub value: u32,
pub value_hex: String,
pub probable_f32_le: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpPostTextGroundedFieldObservation {
pub field_name: String,
pub runtime_object_offset: usize,
pub runtime_object_offset_hex: String,
pub file_offset: usize,
pub file_offset_hex: String,
pub field_width_bytes: usize,
pub field_width_bytes_hex: String,
pub raw_hex: String,
pub value_u8: Option<u8>,
pub value_u8_hex: Option<String>,
pub value_u32: Option<u32>,
pub value_u32_hex: Option<String>,
pub probable_f32_le: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpPostTextFloatAlignmentCandidate {
pub grounded_field_name: String,
pub grounded_field_runtime_object_offset: usize,
pub grounded_field_runtime_object_offset_hex: String,
pub grounded_field_file_offset: usize,
pub grounded_field_file_offset_hex: String,
pub candidate_offset: usize,
pub candidate_offset_hex: String,
pub candidate_value: u32,
pub candidate_value_hex: String,
pub probable_f32_le: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpPostTextFieldNeighborhoodProbe {
pub profile_family: String,
pub source_kind: String,
pub window_offset: usize,
pub window_end_offset: usize,
pub window_len: usize,
pub window_len_hex: String,
pub grounded_field_observations: Vec<SmpPostTextGroundedFieldObservation>,
pub one_byte_early_float_candidates: Vec<SmpPostTextFloatAlignmentCandidate>,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLocomotivePolicyFieldObservation {
pub field_name: String,
pub runtime_object_offset: usize,
pub runtime_object_offset_hex: String,
pub file_offset: usize,
pub file_offset_hex: String,
pub field_width_bytes: usize,
pub field_width_bytes_hex: String,
pub raw_hex: String,
pub value_u8: Option<u8>,
pub value_u8_hex: Option<String>,
pub value_u32: Option<u32>,
pub value_u32_hex: Option<String>,
pub probable_f32_le: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLocomotivePolicyFloatAlignmentCandidate {
pub grounded_field_name: String,
pub grounded_field_runtime_object_offset: usize,
pub grounded_field_runtime_object_offset_hex: String,
pub grounded_field_file_offset: usize,
pub grounded_field_file_offset_hex: String,
pub candidate_offset: usize,
pub candidate_offset_hex: String,
pub candidate_value: u32,
pub candidate_value_hex: String,
pub probable_f32_le: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLocomotivePolicyNeighborhoodProbe {
pub profile_family: String,
pub source_kind: String,
pub window_offset: usize,
pub window_end_offset: usize,
pub window_len: usize,
pub window_len_hex: String,
pub grounded_field_observations: Vec<SmpLocomotivePolicyFieldObservation>,
pub three_byte_early_float_candidates: Vec<SmpLocomotivePolicyFloatAlignmentCandidate>,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpPreRecipeScalarPlateauLane {
pub absolute_offset: usize,
pub relative_offset: usize,
pub absolute_offset_hex: String,
pub relative_offset_hex: String,
pub value: u32,
pub value_hex: String,
pub probable_f32_le: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpPreRecipeScalarPlateauProbe {
pub profile_family: String,
pub source_kind: String,
pub window_offset: usize,
pub window_end_offset: usize,
pub window_len: usize,
pub window_len_hex: String,
pub aligned_dword_count: usize,
pub family_signature: String,
pub nonzero_lanes: Vec<SmpPreRecipeScalarPlateauLane>,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpRecipeBookSummaryBook {
pub book_index: usize,
pub book_offset: usize,
pub book_offset_hex: String,
pub head_kind: String,
pub head_nonzero_byte_count: usize,
pub head_cdcd_byte_count: usize,
pub head_first_16_hex: String,
pub max_annual_production_offset: usize,
pub max_annual_production_offset_hex: String,
pub max_annual_production_word: u32,
pub max_annual_production_word_hex: String,
pub max_annual_production_probable_f32_le: Option<String>,
pub line_area_offset: usize,
pub line_area_offset_hex: String,
pub line_area_len: usize,
pub line_area_len_hex: String,
pub line_area_kind: String,
pub line_area_nonzero_byte_count: usize,
pub line_area_cdcd_byte_count: usize,
pub line_area_first_16_hex: String,
pub lines: Vec<SmpRecipeBookLineSummary>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpRecipeBookLineSummary {
pub line_index: usize,
pub line_offset: usize,
pub line_offset_hex: String,
pub line_kind: String,
pub line_signature_kind: String,
pub imports_to_runtime_descriptor: bool,
pub runtime_import_branch_kind: String,
pub line_nonzero_byte_count: usize,
pub line_cdcd_byte_count: usize,
pub line_first_16_hex: String,
pub mode_word_offset: usize,
pub mode_word_offset_hex: String,
pub mode_word: u32,
pub mode_word_hex: String,
pub annual_amount_offset: usize,
pub annual_amount_offset_hex: String,
pub annual_amount_word: u32,
pub annual_amount_word_hex: String,
pub annual_amount_probable_f32_le: Option<String>,
pub supplied_cargo_token_offset: usize,
pub supplied_cargo_token_offset_hex: String,
pub supplied_cargo_token_word: u32,
pub supplied_cargo_token_word_hex: String,
pub supplied_cargo_token_layout_kind: String,
pub supplied_cargo_token_window_hex: String,
pub supplied_cargo_token_window_ascii: String,
pub supplied_cargo_token_active_in_runtime_import: bool,
pub supplied_cargo_token_probable_high16_ascii_stem: Option<String>,
pub demanded_cargo_token_offset: usize,
pub demanded_cargo_token_offset_hex: String,
pub demanded_cargo_token_word: u32,
pub demanded_cargo_token_word_hex: String,
pub demanded_cargo_token_layout_kind: String,
pub demanded_cargo_token_window_hex: String,
pub demanded_cargo_token_window_ascii: String,
pub demanded_cargo_token_active_in_runtime_import: bool,
pub demanded_cargo_token_probable_high16_ascii_stem: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpRecipeBookSummaryProbe {
pub profile_family: String,
pub source_kind: String,
pub root_offset: usize,
pub root_offset_hex: String,
pub runtime_object_root_offset: usize,
pub runtime_object_root_offset_hex: String,
pub book_count: usize,
pub book_stride: usize,
pub book_stride_hex: String,
pub max_annual_production_relative_offset: usize,
pub max_annual_production_relative_offset_hex: String,
pub line_area_relative_offset: usize,
pub line_area_relative_offset_hex: String,
pub line_count: usize,
pub line_stride: usize,
pub line_stride_hex: String,
pub books: Vec<SmpRecipeBookSummaryBook>,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpPostSpecialConditionsScalarProbe {
pub profile_family: String,
pub source_kind: String,
pub window_offset: usize,
pub window_end_offset: usize,
pub window_len: usize,
pub window_len_hex: String,
pub dword_count: usize,
pub overlap_end_offset: usize,
pub overlap_end_offset_hex: String,
pub overlap_dword_count: usize,
pub overlap_nonzero_dword_count: usize,
pub overlap_nonzero_relative_offset_hexes: Vec<String>,
pub tail_offset: usize,
pub tail_offset_hex: String,
pub tail_len: usize,
pub tail_len_hex: String,
pub tail_dword_count: usize,
pub tail_runtime_object_offset: usize,
pub tail_runtime_object_offset_hex: String,
pub tail_runtime_object_end_offset: usize,
pub tail_runtime_object_end_offset_hex: String,
pub tail_runtime_object_validated_byte_mirror: bool,
pub tail_grounded_live_field_offset: usize,
pub tail_grounded_live_field_offset_hex: String,
pub tail_grounded_live_field_name: String,
pub tail_grounded_live_field_copy_len: usize,
pub tail_grounded_live_field_copy_len_hex: String,
pub tail_grounded_live_field_copy_end_offset: usize,
pub tail_grounded_live_field_copy_end_offset_hex: String,
pub tail_window_cuts_through_grounded_live_field: bool,
pub tail_grounded_live_field_remaining_file_window_offset: usize,
pub tail_grounded_live_field_remaining_file_window_offset_hex: String,
pub tail_grounded_live_field_remaining_file_window_len: usize,
pub tail_grounded_live_field_remaining_file_window_len_hex: String,
pub tail_grounded_live_field_remaining_file_window_nonzero_byte_count: usize,
pub tail_grounded_live_field_remaining_file_window_first_nonzero_offset: Option<usize>,
pub tail_grounded_live_field_remaining_file_window_first_nonzero_offset_hex: Option<String>,
pub tail_grounded_live_field_remaining_file_window_last_nonzero_offset: Option<usize>,
pub tail_grounded_live_field_remaining_file_window_last_nonzero_offset_hex: Option<String>,
pub tail_next_grounded_dword_field_offset: usize,
pub tail_next_grounded_dword_field_offset_hex: String,
pub tail_next_grounded_dword_field_file_offset: usize,
pub tail_next_grounded_dword_field_file_offset_hex: String,
pub tail_second_grounded_dword_field_offset: usize,
pub tail_second_grounded_dword_field_offset_hex: String,
pub tail_second_grounded_dword_field_file_offset: usize,
pub tail_second_grounded_dword_field_file_offset_hex: String,
pub post_text_field_file_alignment_matches_grounded_dword_fields: bool,
pub tail_nonzero_dword_count: usize,
pub tail_first_nonzero_offset: Option<usize>,
pub tail_first_nonzero_offset_hex: Option<String>,
pub tail_last_nonzero_offset: Option<usize>,
pub tail_last_nonzero_offset_hex: Option<String>,
pub tail_nonzero_relative_offset_hexes: Vec<String>,
pub nonzero_dword_count: usize,
pub first_nonzero_offset: Option<usize>,
pub first_nonzero_offset_hex: Option<String>,
pub last_nonzero_offset: Option<usize>,
pub last_nonzero_offset_hex: Option<String>,
pub nonzero_lanes: Vec<SmpPostSpecialConditionsScalarLane>,
pub evidence: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpClassicRehydrateProfileProbe {
pub profile_family: String,
pub progress_32dc_offset: usize,
pub progress_3714_offset: usize,
pub progress_3715_offset: usize,
pub packed_profile_offset: usize,
pub packed_profile_len: usize,
pub packed_profile_len_hex: String,
pub packed_profile_block: SmpClassicPackedProfileBlock,
pub ascii_runs: Vec<SmpAsciiPreview>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpClassicPackedProfileBlock {
pub relative_len: usize,
pub relative_len_hex: String,
pub leading_word_0: u32,
pub leading_word_0_hex: String,
pub trailing_zero_word_count_after_leading_word: usize,
pub map_path_offset: usize,
pub map_path: Option<String>,
pub display_name_offset: usize,
pub display_name: Option<String>,
pub profile_byte_0x77: u8,
pub profile_byte_0x77_hex: String,
pub profile_byte_0x82: u8,
pub profile_byte_0x82_hex: String,
pub profile_byte_0x97: u8,
pub profile_byte_0x97_hex: String,
pub profile_byte_0xc5: u8,
pub profile_byte_0xc5_hex: String,
pub stable_nonzero_words: Vec<SmpPackedProfileWordLane>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpRt3105PackedProfileProbe {
pub profile_family: String,
pub packed_profile_offset: usize,
pub packed_profile_len: usize,
pub packed_profile_len_hex: String,
pub packed_profile_block: SmpRt3105PackedProfileBlock,
pub ascii_runs: Vec<SmpAsciiPreview>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpRt3105PackedProfileBlock {
pub relative_len: usize,
pub relative_len_hex: String,
pub leading_word_0: u32,
pub leading_word_0_hex: String,
pub trailing_zero_word_count_after_leading_word: usize,
pub header_flag_word_3: u32,
pub header_flag_word_3_hex: String,
pub map_path_offset: usize,
pub map_path: Option<String>,
pub display_name_offset: usize,
pub display_name: Option<String>,
pub profile_byte_0x77: u8,
pub profile_byte_0x77_hex: String,
pub profile_byte_0x82: u8,
pub profile_byte_0x82_hex: String,
pub profile_byte_0x97: u8,
pub profile_byte_0x97_hex: String,
pub profile_byte_0xc5: u8,
pub profile_byte_0xc5_hex: String,
pub stable_nonzero_words: Vec<SmpPackedProfileWordLane>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpSaveLoadCandidateTableSummary {
pub source_kind: String,
pub semantic_family: String,
pub observed_entry_count: usize,
pub zero_availability_count: usize,
pub zero_availability_names: Vec<String>,
pub footer_progress_hex_words: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpSaveLoadSummary {
pub file_extension_hint: Option<String>,
pub container_profile_family: Option<String>,
pub mechanism_family: String,
pub mechanism_confidence: String,
pub packed_profile_kind: Option<String>,
pub packed_profile_family: Option<String>,
pub packed_profile_offset: Option<usize>,
pub packed_profile_len: Option<usize>,
pub map_path: Option<String>,
pub display_name: Option<String>,
pub profile_byte_0x77: Option<u8>,
pub profile_byte_0x77_hex: Option<String>,
pub profile_byte_0x82: Option<u8>,
pub profile_byte_0x82_hex: Option<String>,
pub profile_byte_0x97: Option<u8>,
pub profile_byte_0x97_hex: Option<String>,
pub profile_byte_0xc5: Option<u8>,
pub profile_byte_0xc5_hex: Option<String>,
pub trailer_family: Option<String>,
pub bridge_family: Option<String>,
pub candidate_table: Option<SmpSaveLoadCandidateTableSummary>,
pub notes: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedProfile {
pub profile_kind: String,
pub profile_family: String,
pub packed_profile_offset: usize,
pub packed_profile_len: usize,
pub packed_profile_len_hex: String,
pub leading_word_0: u32,
pub leading_word_0_hex: String,
pub header_flag_word_3: Option<u32>,
pub header_flag_word_3_hex: Option<String>,
pub map_path: Option<String>,
pub display_name: Option<String>,
pub profile_byte_0x77: u8,
pub profile_byte_0x77_hex: String,
pub profile_byte_0x82: u8,
pub profile_byte_0x82_hex: String,
pub profile_byte_0x97: u8,
pub profile_byte_0x97_hex: String,
pub profile_byte_0xc5: u8,
pub profile_byte_0xc5_hex: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedCandidateAvailabilityTable {
pub source_kind: String,
pub semantic_family: String,
pub header_offset: usize,
pub entries_offset: usize,
pub entries_end_offset: usize,
pub observed_entry_count: usize,
pub zero_availability_count: usize,
pub zero_availability_names: Vec<String>,
pub footer_progress_hex_words: Vec<String>,
pub entries: Vec<SmpRt3105SaveNameTableEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedNamedLocomotiveAvailabilityTable {
pub source_kind: String,
pub semantic_family: String,
#[serde(default)]
pub header_offset: Option<usize>,
#[serde(default)]
pub entries_offset: Option<usize>,
#[serde(default)]
pub entries_end_offset: Option<usize>,
pub observed_entry_count: usize,
pub zero_availability_count: usize,
pub zero_availability_names: Vec<String>,
pub entries: Vec<SmpRt3105SaveNameTableEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedLocomotiveCatalogEntry {
pub locomotive_id: u32,
pub name: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedLocomotiveCatalog {
pub source_kind: String,
pub semantic_family: String,
#[serde(default)]
pub entries_offset: Option<usize>,
pub observed_entry_count: usize,
pub entries: Vec<SmpLoadedLocomotiveCatalogEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedCargoCatalogEntry {
pub slot_id: u32,
pub label: String,
#[serde(default)]
pub cargo_class: RuntimeCargoClass,
pub book_index: usize,
pub max_annual_production_word: u32,
pub mode_word: u32,
pub runtime_import_branch_kind: String,
pub annual_amount_word: u32,
pub supplied_cargo_token_word: u32,
#[serde(default)]
pub supplied_cargo_token_probable_high16_ascii_stem: Option<String>,
pub demanded_cargo_token_word: u32,
#[serde(default)]
pub demanded_cargo_token_probable_high16_ascii_stem: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedCargoCatalog {
pub source_kind: String,
pub semantic_family: String,
#[serde(default)]
pub root_offset: Option<usize>,
pub observed_entry_count: usize,
pub entries: Vec<SmpLoadedCargoCatalogEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedWorldIssue37State {
pub source_kind: String,
pub semantic_family: String,
pub issue_value: u32,
pub issue_value_hex: String,
pub issue_38_value: u32,
pub issue_38_value_hex: String,
pub issue_39_value: u32,
pub issue_39_value_hex: String,
pub issue_3a_value: u32,
pub issue_3a_value_hex: String,
pub multiplier_raw_u32: u32,
pub multiplier_raw_hex: String,
pub multiplier_value_f32_text: String,
#[serde(default)]
pub issue_opinion_base_terms_raw_i32: Vec<i32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedWorldEconomicTuningState {
pub source_kind: String,
pub semantic_family: String,
pub mirror_raw_u32: u32,
pub mirror_raw_hex: String,
pub mirror_value_f32_text: String,
pub lane_raw_u32: Vec<u32>,
pub lane_raw_hex: Vec<String>,
pub lane_value_f32_text: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedWorldFinanceNeighborhoodState {
pub source_kind: String,
pub semantic_family: String,
pub packed_year_word_raw_u16: u16,
pub packed_year_word_raw_hex: String,
pub partial_year_progress_raw_u8: u8,
pub partial_year_progress_raw_hex: String,
pub current_calendar_tuple_word_raw_u32: u32,
pub current_calendar_tuple_word_raw_hex: String,
pub current_calendar_tuple_word_2_raw_u32: u32,
pub current_calendar_tuple_word_2_raw_hex: String,
pub absolute_counter_raw_u32: u32,
pub absolute_counter_raw_hex: String,
pub absolute_counter_mirror_raw_u32: u32,
pub absolute_counter_mirror_raw_hex: String,
pub stock_policy_raw_u8: u8,
pub stock_policy_raw_hex: String,
pub bond_policy_raw_u8: u8,
pub bond_policy_raw_hex: String,
pub bankruptcy_policy_raw_u8: u8,
pub bankruptcy_policy_raw_hex: String,
pub dividend_policy_raw_u8: u8,
pub dividend_policy_raw_hex: String,
pub building_density_growth_setting_raw_u32: u32,
pub building_density_growth_setting_raw_hex: String,
pub labels: Vec<String>,
pub relative_offsets: Vec<usize>,
pub relative_offset_hex: Vec<String>,
pub raw_u32: Vec<u32>,
pub raw_hex: Vec<String>,
pub value_i32: Vec<i32>,
pub value_f32_text: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedWorldLocomotivePolicyState {
pub source_kind: String,
pub semantic_family: String,
#[serde(default)]
pub selected_year_gap_scalar_raw_u32: Option<u32>,
#[serde(default)]
pub selected_year_gap_scalar_raw_hex: Option<String>,
#[serde(default)]
pub selected_year_gap_scalar_value_f32_text: Option<String>,
#[serde(default)]
pub linked_site_removal_follow_on_gate_raw_u8: Option<u8>,
#[serde(default)]
pub linked_site_removal_follow_on_gate_raw_hex: Option<String>,
#[serde(default)]
pub auto_show_grade_during_track_lay_raw_u8: Option<u8>,
#[serde(default)]
pub auto_show_grade_during_track_lay_raw_hex: Option<String>,
#[serde(default)]
pub starting_building_density_level_raw_u8: Option<u8>,
#[serde(default)]
pub starting_building_density_level_raw_hex: Option<String>,
#[serde(default)]
pub building_density_growth_raw_u8: Option<u8>,
#[serde(default)]
pub building_density_growth_raw_hex: Option<String>,
#[serde(default)]
pub leftover_simulation_time_accumulator_raw_u32: Option<u32>,
#[serde(default)]
pub leftover_simulation_time_accumulator_raw_hex: Option<String>,
#[serde(default)]
pub leftover_simulation_time_accumulator_value_f32_text: Option<String>,
#[serde(default)]
pub selected_year_lane_snapshot_raw_u8: Option<u8>,
#[serde(default)]
pub selected_year_lane_snapshot_raw_hex: Option<String>,
#[serde(default)]
pub all_steam_locomotives_available_raw_u8: Option<u8>,
#[serde(default)]
pub all_steam_locomotives_available_raw_hex: Option<String>,
#[serde(default)]
pub all_diesel_locomotives_available_raw_u8: Option<u8>,
#[serde(default)]
pub all_diesel_locomotives_available_raw_hex: Option<String>,
#[serde(default)]
pub all_electric_locomotives_available_raw_u8: Option<u8>,
#[serde(default)]
pub all_electric_locomotives_available_raw_hex: Option<String>,
#[serde(default)]
pub cached_available_locomotive_rating_raw_u32: Option<u32>,
#[serde(default)]
pub cached_available_locomotive_rating_raw_hex: Option<String>,
#[serde(default)]
pub cached_available_locomotive_rating_value_f32_text: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedCompanyRosterEntry {
pub company_id: u32,
pub active: bool,
#[serde(default)]
pub controller_kind: RuntimeCompanyControllerKind,
pub current_cash: i64,
pub debt: u64,
#[serde(default)]
pub credit_rating_score: Option<i64>,
#[serde(default)]
pub prime_rate: Option<i64>,
#[serde(default)]
pub available_track_laying_capacity: Option<u32>,
#[serde(default)]
pub track_piece_counts: RuntimeTrackPieceCounts,
#[serde(default)]
pub linked_chairman_profile_id: Option<u32>,
#[serde(default)]
pub book_value_per_share: i64,
#[serde(default)]
pub investor_confidence: i64,
#[serde(default)]
pub management_attitude: i64,
#[serde(default)]
pub takeover_cooldown_year: Option<u32>,
#[serde(default)]
pub merger_cooldown_year: Option<u32>,
#[serde(default)]
pub preferred_locomotive_engine_type_raw_u8: Option<u8>,
#[serde(default)]
pub market_state: Option<RuntimeCompanyMarketState>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedCompanyRoster {
pub source_kind: String,
pub semantic_family: String,
pub observed_entry_count: usize,
#[serde(default)]
pub selected_company_id: Option<u32>,
pub entries: Vec<SmpLoadedCompanyRosterEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedChairmanProfileEntry {
pub profile_id: u32,
pub name: String,
pub active: bool,
#[serde(default)]
pub current_cash: i64,
#[serde(default)]
pub linked_company_id: Option<u32>,
#[serde(default)]
pub company_holdings: BTreeMap<u32, u32>,
#[serde(default)]
pub holdings_value_total: i64,
#[serde(default)]
pub net_worth_total: i64,
#[serde(default)]
pub purchasing_power_total: i64,
#[serde(default)]
pub personality_byte_0x291: Option<u8>,
#[serde(default)]
pub issue_opinion_terms_raw_i32: Vec<i32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedChairmanProfileTable {
pub source_kind: String,
pub semantic_family: String,
pub observed_entry_count: usize,
#[serde(default)]
pub selected_chairman_profile_id: Option<u32>,
pub entries: Vec<SmpLoadedChairmanProfileEntry>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SmpSaveScalarCandidate {
pub relative_offset: usize,
pub relative_offset_hex: String,
pub raw_u64: u64,
pub raw_u64_hex: String,
pub value_i64: i64,
pub value_f64: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SmpSaveDwordCandidate {
pub label: String,
pub relative_offset: usize,
pub relative_offset_hex: String,
pub raw_u32: u32,
pub raw_u32_hex: String,
pub value_i32: i32,
pub value_f32: f32,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpSaveWorldSelectionRoleAnalysisEntry {
pub slot_index: usize,
pub selector_byte: u8,
pub selector_byte_hex: String,
pub role_gate_byte: u8,
pub role_gate_byte_hex: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpSaveWorldSelectionRoleAnalysis {
pub selected_company_id: u32,
pub selected_chairman_profile_id: u32,
pub campaign_override_flag: u8,
pub campaign_override_flag_hex: String,
pub chairman_slots: Vec<SmpSaveWorldSelectionRoleAnalysisEntry>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SmpSaveCompanyRecordAnalysisEntry {
pub company_id: u32,
pub name: String,
pub active: bool,
#[serde(default)]
pub linked_chairman_profile_id: Option<u32>,
pub outstanding_shares: u32,
pub debt: u64,
pub bond_count: u8,
#[serde(default)]
pub live_bond_slots: Vec<crate::RuntimeCompanyBondSlot>,
#[serde(default)]
pub largest_live_bond_principal: Option<u32>,
#[serde(default)]
pub highest_coupon_live_bond_principal: Option<u32>,
#[serde(default)]
pub available_track_laying_capacity: Option<u32>,
pub company_value_scalar_f32: f32,
pub cached_share_support_scalar_f32: f32,
pub cached_share_price_f32: f32,
pub chairman_salary_baseline: u32,
pub chairman_salary_current: u32,
pub chairman_bonus_year: u32,
pub chairman_bonus_amount: i32,
pub founding_year: u32,
pub last_bankruptcy_year: u32,
pub last_dividend_year: u32,
pub preferred_locomotive_engine_type_raw_u8: u8,
pub preferred_locomotive_engine_type_raw_hex: String,
pub city_connection_latch: bool,
pub linked_transit_latch: bool,
pub merger_cooldown_year: u32,
pub takeover_cooldown_year: u32,
#[serde(default)]
pub scalar_dword_candidates: Vec<SmpSaveDwordCandidate>,
#[serde(default)]
pub post_capacity_dword_candidates: Vec<SmpSaveDwordCandidate>,
#[serde(default)]
pub stat_band_root_0cfb_candidates: Vec<SmpSaveDwordCandidate>,
#[serde(default)]
pub stat_band_root_0d7f_candidates: Vec<SmpSaveDwordCandidate>,
#[serde(default)]
pub stat_band_root_1c47_candidates: Vec<SmpSaveDwordCandidate>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SmpSaveChairmanRecordAnalysisEntry {
pub profile_id: u32,
pub name: String,
pub active: bool,
pub current_cash: f64,
#[serde(default)]
pub linked_company_id: Option<u32>,
#[serde(default)]
pub holdings_by_company: BTreeMap<u32, u32>,
#[serde(default)]
pub derived_holdings_share_price_total: Option<i64>,
#[serde(default)]
pub derived_net_worth_share_price_total: Option<i64>,
#[serde(default)]
pub derived_cached_purchasing_power_total: Option<i64>,
pub personality_byte_0x291: u8,
pub personality_byte_0x291_hex: String,
#[serde(default)]
pub cached_scalar_candidates: Vec<SmpSaveScalarCandidate>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SmpSaveCompanyChairmanAnalysisReport {
pub profile_family: String,
#[serde(default)]
pub selected_company_id: Option<u32>,
#[serde(default)]
pub selected_chairman_profile_id: Option<u32>,
#[serde(default)]
pub world_selection_context: Option<SmpSaveWorldSelectionRoleAnalysis>,
#[serde(default)]
pub world_issue_37: Option<SmpSaveWorldIssue37Probe>,
#[serde(default)]
pub world_economic_tuning: Option<SmpSaveWorldEconomicTuningProbe>,
#[serde(default)]
pub world_finance_neighborhood: Option<SmpSaveWorldFinanceNeighborhoodProbe>,
#[serde(default)]
pub region_collection_header: Option<SmpSaveTaggedCollectionHeaderProbe>,
#[serde(default)]
pub region_collection_directory: Option<SmpSaveRegionCollectionDirectoryProbe>,
#[serde(default)]
pub placed_structure_collection_header: Option<SmpSaveTaggedCollectionHeaderProbe>,
#[serde(default)]
pub company_entries: Vec<SmpSaveCompanyRecordAnalysisEntry>,
#[serde(default)]
pub chairman_entries: Vec<SmpSaveChairmanRecordAnalysisEntry>,
#[serde(default)]
pub notes: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedSpecialConditionsTable {
pub source_kind: String,
pub table_offset: usize,
pub table_len: usize,
pub enabled_visible_count: usize,
pub enabled_visible_labels: Vec<String>,
pub entries: Vec<SmpSpecialConditionEntry>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedEventRuntimeCollectionSummary {
pub source_kind: String,
pub mechanism_family: String,
pub mechanism_confidence: String,
#[serde(default)]
pub container_profile_family: Option<String>,
pub metadata_tag_offset: usize,
pub records_tag_offset: usize,
pub close_tag_offset: usize,
pub packed_state_version: u32,
pub packed_state_version_hex: String,
pub live_id_bound: u32,
pub live_record_count: usize,
pub live_entry_ids: Vec<u32>,
#[serde(default)]
pub decoded_record_count: usize,
#[serde(default)]
pub imported_runtime_record_count: usize,
#[serde(default)]
pub records: Vec<SmpLoadedPackedEventRecordSummary>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedPackedEventRecordSummary {
pub record_index: usize,
pub live_entry_id: u32,
#[serde(default)]
pub payload_offset: Option<usize>,
#[serde(default)]
pub payload_len: Option<usize>,
pub decode_status: String,
#[serde(default)]
pub payload_family: String,
#[serde(default)]
pub trigger_kind: Option<u8>,
#[serde(default)]
pub active: Option<bool>,
#[serde(default)]
pub marks_collection_dirty: Option<bool>,
#[serde(default)]
pub one_shot: Option<bool>,
#[serde(default)]
pub compact_control: Option<SmpLoadedPackedEventCompactControlSummary>,
#[serde(default)]
pub text_bands: Vec<SmpLoadedPackedEventTextBandSummary>,
#[serde(default)]
pub standalone_condition_row_count: usize,
#[serde(default)]
pub standalone_condition_rows: Vec<SmpLoadedPackedEventConditionRowSummary>,
#[serde(default)]
pub negative_sentinel_scope: Option<SmpLoadedPackedEventNegativeSentinelScopeSummary>,
#[serde(default)]
pub grouped_effect_row_counts: Vec<usize>,
#[serde(default)]
pub grouped_effect_rows: Vec<SmpLoadedPackedEventGroupedEffectRowSummary>,
#[serde(default)]
pub decoded_conditions: Vec<RuntimeCondition>,
#[serde(default)]
pub decoded_actions: Vec<RuntimeEffect>,
#[serde(default)]
pub executable_import_ready: bool,
#[serde(default)]
pub notes: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedPackedEventNegativeSentinelScopeSummary {
pub company_test_scope: RuntimeCompanyConditionTestScope,
pub player_test_scope: RuntimePlayerConditionTestScope,
pub territory_scope_selector_is_0x63: bool,
#[serde(default)]
pub source_row_indexes: Vec<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedPackedEventCompactControlSummary {
pub mode_byte_0x7ef: u8,
pub primary_selector_0x7f0: u32,
pub grouped_mode_0x7f4: u8,
pub one_shot_header_0x7f5: u32,
pub modifier_flag_0x7f9: u8,
pub modifier_flag_0x7fa: u8,
pub grouped_target_scope_ordinals_0x7fb: Vec<u8>,
pub grouped_scope_checkboxes_0x7ff: Vec<u8>,
pub summary_toggle_0x800: u8,
pub grouped_territory_selectors_0x80f: Vec<i32>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedPackedEventTextBandSummary {
pub label: String,
pub packed_len: usize,
pub present: bool,
pub preview: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedPackedEventConditionRowSummary {
pub row_index: usize,
pub raw_condition_id: i32,
pub subtype: u8,
#[serde(default)]
pub flag_bytes: Vec<u8>,
#[serde(default)]
pub candidate_name: Option<String>,
#[serde(default)]
pub comparator: Option<String>,
#[serde(default)]
pub metric: Option<String>,
#[serde(default)]
pub semantic_family: Option<String>,
#[serde(default)]
pub semantic_preview: Option<String>,
#[serde(default)]
pub recovered_cargo_slot: Option<u32>,
#[serde(default)]
pub recovered_cargo_class: Option<String>,
#[serde(default)]
pub requires_candidate_name_binding: bool,
#[serde(default)]
pub notes: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedPackedEventGroupedEffectRowSummary {
pub group_index: usize,
pub row_index: usize,
pub descriptor_id: u32,
#[serde(default)]
pub descriptor_label: Option<String>,
#[serde(default)]
pub target_mask_bits: Option<u8>,
#[serde(default)]
pub parameter_family: Option<String>,
#[serde(default)]
pub grouped_target_subject: Option<String>,
#[serde(default)]
pub grouped_target_scope: Option<String>,
pub opcode: u8,
pub raw_scalar_value: i32,
pub value_byte_0x09: u8,
pub value_dword_0x0d: u32,
pub value_byte_0x11: u8,
pub value_byte_0x12: u8,
pub value_word_0x14: u16,
pub value_word_0x16: u16,
pub row_shape: String,
#[serde(default)]
pub semantic_family: Option<String>,
#[serde(default)]
pub semantic_preview: Option<String>,
#[serde(default)]
pub recovered_cargo_slot: Option<u32>,
#[serde(default)]
pub recovered_cargo_class: Option<String>,
#[serde(default)]
pub recovered_cargo_label: Option<String>,
#[serde(default)]
pub recovered_locomotive_id: Option<u32>,
#[serde(default)]
pub locomotive_name: Option<String>,
#[serde(default)]
pub notes: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RealGroupedTargetSubject {
Company,
Player,
Chairman,
Territory,
WholeGame,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedSaveSlice {
pub file_extension_hint: Option<String>,
pub container_profile_family: Option<String>,
pub mechanism_family: String,
pub mechanism_confidence: String,
pub trailer_family: Option<String>,
pub bridge_family: Option<String>,
pub profile: Option<SmpLoadedProfile>,
pub candidate_availability_table: Option<SmpLoadedCandidateAvailabilityTable>,
pub named_locomotive_availability_table: Option<SmpLoadedNamedLocomotiveAvailabilityTable>,
#[serde(default)]
pub locomotive_catalog: Option<SmpLoadedLocomotiveCatalog>,
#[serde(default)]
pub cargo_catalog: Option<SmpLoadedCargoCatalog>,
#[serde(default)]
pub world_issue_37_state: Option<SmpLoadedWorldIssue37State>,
#[serde(default)]
pub world_economic_tuning_state: Option<SmpLoadedWorldEconomicTuningState>,
#[serde(default)]
pub world_finance_neighborhood_state: Option<SmpLoadedWorldFinanceNeighborhoodState>,
#[serde(default)]
pub world_locomotive_policy_state: Option<SmpLoadedWorldLocomotivePolicyState>,
#[serde(default)]
pub company_roster: Option<SmpLoadedCompanyRoster>,
#[serde(default)]
pub chairman_profile_table: Option<SmpLoadedChairmanProfileTable>,
pub special_conditions_table: Option<SmpLoadedSpecialConditionsTable>,
pub event_runtime_collection: Option<SmpLoadedEventRuntimeCollectionSummary>,
pub notes: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpPackedProfileWordLane {
pub relative_offset: usize,
pub relative_offset_hex: String,
pub value: u32,
pub value_hex: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SmpInspectionReport {
pub inspection_mode: String,
pub file_extension_hint: Option<String>,
pub file_size: usize,
pub sha256: String,
pub preamble: SmpPreamble,
pub shared_header: Option<SmpSharedHeader>,
pub header_variant_probe: Option<SmpHeaderVariantProbe>,
pub first_ascii_run: Option<SmpAsciiPreview>,
pub early_content_probe: Option<SmpEarlyContentProbe>,
pub secondary_variant_probe: Option<SmpSecondaryVariantProbe>,
pub container_profile: Option<SmpContainerProfile>,
pub save_bootstrap_block: Option<SmpSaveBootstrapBlock>,
pub save_anchor_run_block: Option<SmpSaveAnchorRunBlock>,
pub runtime_anchor_cycle_block: Option<SmpRuntimeAnchorCycleBlock>,
pub runtime_trailer_block: Option<SmpRuntimeTrailerBlock>,
pub runtime_post_span_probe: Option<SmpRuntimePostSpanProbe>,
pub rt3_105_post_span_bridge_probe: Option<SmpRt3105PostSpanBridgeProbe>,
pub rt3_105_save_bridge_payload_probe: Option<SmpRt3105SaveBridgePayloadProbe>,
pub save_world_selection_context_probe: Option<SmpSaveWorldSelectionContextProbe>,
pub save_world_issue_37_probe: Option<SmpSaveWorldIssue37Probe>,
pub save_world_economic_tuning_probe: Option<SmpSaveWorldEconomicTuningProbe>,
pub save_world_finance_neighborhood_probe: Option<SmpSaveWorldFinanceNeighborhoodProbe>,
pub save_company_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
pub save_chairman_profile_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
pub save_region_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
pub save_region_collection_directory_probe: Option<SmpSaveRegionCollectionDirectoryProbe>,
pub save_placed_structure_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
#[serde(default)]
pub save_company_roster_probe: Option<SmpLoadedCompanyRoster>,
#[serde(default)]
pub save_chairman_profile_table_probe: Option<SmpLoadedChairmanProfileTable>,
pub rt3_105_save_name_table_probe: Option<SmpRt3105SaveNameTableProbe>,
pub rt3_105_save_named_locomotive_availability_probe:
Option<SmpRt3105SaveNamedLocomotiveAvailabilityProbe>,
pub special_conditions_probe: Option<SmpSpecialConditionsProbe>,
pub smp_aligned_runtime_rule_band_probe: Option<SmpAlignedRuntimeRuleBandProbe>,
pub post_special_conditions_scalar_probe: Option<SmpPostSpecialConditionsScalarProbe>,
pub post_text_field_neighborhood_probe: Option<SmpPostTextFieldNeighborhoodProbe>,
pub locomotive_policy_neighborhood_probe: Option<SmpLocomotivePolicyNeighborhoodProbe>,
pub pre_recipe_scalar_plateau_probe: Option<SmpPreRecipeScalarPlateauProbe>,
pub recipe_book_summary_probe: Option<SmpRecipeBookSummaryProbe>,
pub classic_rehydrate_profile_probe: Option<SmpClassicRehydrateProfileProbe>,
pub rt3_105_packed_profile_probe: Option<SmpRt3105PackedProfileProbe>,
pub save_load_summary: Option<SmpSaveLoadSummary>,
pub event_runtime_collection_summary: Option<SmpLoadedEventRuntimeCollectionSummary>,
pub contains_grounded_runtime_tags: bool,
pub known_tag_hits: Vec<SmpKnownTagHit>,
pub notes: Vec<String>,
pub warnings: Vec<String>,
}
pub fn inspect_smp_file(path: &Path) -> Result<SmpInspectionReport, Box<dyn std::error::Error>> {
let bytes = fs::read(path)?;
Ok(inspect_bundle_bytes(
&bytes,
path.extension()
.and_then(|extension| extension.to_str())
.map(|extension| extension.to_ascii_lowercase()),
))
}
pub fn inspect_smp_bytes(bytes: &[u8]) -> SmpInspectionReport {
inspect_bundle_bytes(bytes, None)
}
pub fn load_save_slice_file(path: &Path) -> Result<SmpLoadedSaveSlice, Box<dyn std::error::Error>> {
let inspection = inspect_smp_file(path)?;
load_save_slice_from_report(&inspection)
.map_err(|err| -> Box<dyn std::error::Error> { err.into() })
}
pub fn load_save_slice_from_report(
report: &SmpInspectionReport,
) -> Result<SmpLoadedSaveSlice, String> {
let summary = report
.save_load_summary
.as_ref()
.ok_or_else(|| "inspection did not expose a recognizable save-load summary".to_string())?;
let profile = if let Some(probe) = &report.classic_rehydrate_profile_probe {
Some(SmpLoadedProfile {
profile_kind: "classic-rehydrate-profile".to_string(),
profile_family: probe.profile_family.clone(),
packed_profile_offset: probe.packed_profile_offset,
packed_profile_len: probe.packed_profile_len,
packed_profile_len_hex: probe.packed_profile_len_hex.clone(),
leading_word_0: probe.packed_profile_block.leading_word_0,
leading_word_0_hex: probe.packed_profile_block.leading_word_0_hex.clone(),
header_flag_word_3: None,
header_flag_word_3_hex: None,
map_path: probe.packed_profile_block.map_path.clone(),
display_name: probe.packed_profile_block.display_name.clone(),
profile_byte_0x77: probe.packed_profile_block.profile_byte_0x77,
profile_byte_0x77_hex: probe.packed_profile_block.profile_byte_0x77_hex.clone(),
profile_byte_0x82: probe.packed_profile_block.profile_byte_0x82,
profile_byte_0x82_hex: probe.packed_profile_block.profile_byte_0x82_hex.clone(),
profile_byte_0x97: probe.packed_profile_block.profile_byte_0x97,
profile_byte_0x97_hex: probe.packed_profile_block.profile_byte_0x97_hex.clone(),
profile_byte_0xc5: probe.packed_profile_block.profile_byte_0xc5,
profile_byte_0xc5_hex: probe.packed_profile_block.profile_byte_0xc5_hex.clone(),
})
} else {
report
.rt3_105_packed_profile_probe
.as_ref()
.map(|probe| SmpLoadedProfile {
profile_kind: "rt3-105-packed-profile".to_string(),
profile_family: probe.profile_family.clone(),
packed_profile_offset: probe.packed_profile_offset,
packed_profile_len: probe.packed_profile_len,
packed_profile_len_hex: probe.packed_profile_len_hex.clone(),
leading_word_0: probe.packed_profile_block.leading_word_0,
leading_word_0_hex: probe.packed_profile_block.leading_word_0_hex.clone(),
header_flag_word_3: Some(probe.packed_profile_block.header_flag_word_3),
header_flag_word_3_hex: Some(
probe.packed_profile_block.header_flag_word_3_hex.clone(),
),
map_path: probe.packed_profile_block.map_path.clone(),
display_name: probe.packed_profile_block.display_name.clone(),
profile_byte_0x77: probe.packed_profile_block.profile_byte_0x77,
profile_byte_0x77_hex: probe.packed_profile_block.profile_byte_0x77_hex.clone(),
profile_byte_0x82: probe.packed_profile_block.profile_byte_0x82,
profile_byte_0x82_hex: probe.packed_profile_block.profile_byte_0x82_hex.clone(),
profile_byte_0x97: probe.packed_profile_block.profile_byte_0x97,
profile_byte_0x97_hex: probe.packed_profile_block.profile_byte_0x97_hex.clone(),
profile_byte_0xc5: probe.packed_profile_block.profile_byte_0xc5,
profile_byte_0xc5_hex: probe.packed_profile_block.profile_byte_0xc5_hex.clone(),
})
};
let candidate_availability_table = report.rt3_105_save_name_table_probe.as_ref().map(|probe| {
SmpLoadedCandidateAvailabilityTable {
source_kind: probe.source_kind.clone(),
semantic_family: probe.semantic_family.clone(),
header_offset: probe.header_offset,
entries_offset: probe.entries_offset,
entries_end_offset: probe.entries_end_offset,
observed_entry_count: probe.observed_entry_count,
zero_availability_count: probe.zero_trailer_entry_count,
zero_availability_names: probe.zero_trailer_entry_names.clone(),
footer_progress_hex_words: vec![
probe.footer_progress_word_0_hex.clone(),
probe.footer_progress_word_1_hex.clone(),
],
entries: probe.entries.clone(),
}
});
let named_locomotive_availability_table = report
.rt3_105_save_named_locomotive_availability_probe
.as_ref()
.map(|probe| SmpLoadedNamedLocomotiveAvailabilityTable {
source_kind: probe.source_kind.clone(),
semantic_family: probe.semantic_family.clone(),
header_offset: None,
entries_offset: Some(probe.entries_offset),
entries_end_offset: Some(probe.entries_end_offset),
observed_entry_count: probe.observed_entry_count,
zero_availability_count: probe.zero_availability_count,
zero_availability_names: probe.zero_availability_names.clone(),
entries: probe.entries.clone(),
});
let locomotive_catalog = named_locomotive_availability_table
.as_ref()
.and_then(derive_locomotive_catalog_from_named_availability_table);
let cargo_catalog = report
.recipe_book_summary_probe
.as_ref()
.and_then(derive_cargo_catalog_from_recipe_book_probe);
let world_issue_37_state = report
.save_world_issue_37_probe
.as_ref()
.map(derive_loaded_world_issue_37_state_from_probe);
let world_economic_tuning_state = report
.save_world_economic_tuning_probe
.as_ref()
.map(derive_loaded_world_economic_tuning_state_from_probe);
let world_finance_neighborhood_state = report
.save_world_finance_neighborhood_probe
.as_ref()
.map(derive_loaded_world_finance_neighborhood_state_from_probe);
let world_locomotive_policy_state = derive_loaded_world_locomotive_policy_state_from_probes(
report.post_text_field_neighborhood_probe.as_ref(),
report.locomotive_policy_neighborhood_probe.as_ref(),
);
let company_roster = report.save_company_roster_probe.clone().or_else(|| {
report
.save_world_selection_context_probe
.as_ref()
.and_then(|probe| {
derive_selection_only_company_roster_from_save_world_probe(
probe,
report.save_company_collection_header_probe.as_ref(),
)
})
});
let chairman_profile_table = report
.save_chairman_profile_table_probe
.clone()
.or_else(|| {
report
.save_world_selection_context_probe
.as_ref()
.and_then(|probe| {
derive_selection_only_chairman_profile_table_from_save_world_probe(
probe,
report
.save_chairman_profile_collection_header_probe
.as_ref(),
)
})
});
let special_conditions_table =
report
.special_conditions_probe
.as_ref()
.map(|probe| SmpLoadedSpecialConditionsTable {
source_kind: probe.source_kind.clone(),
table_offset: probe.table_offset,
table_len: probe.table_len,
enabled_visible_count: probe.enabled_visible_count,
enabled_visible_labels: probe.enabled_visible_labels.clone(),
entries: probe.entries.clone(),
});
let mut notes = summary.notes.clone();
if let Some(probe) = &report.save_world_selection_context_probe {
notes.push(format!(
"Raw save fixed world block exposes selected_company_id={} at file offset 0x{:x}.",
probe.selected_company_id, probe.selected_company_id_offset
));
notes.push(format!(
"Raw save fixed world block exposes selected_chairman_profile_id={} at file offset 0x{:x}.",
probe.selected_chairman_profile_id, probe.selected_chairman_profile_id_offset
));
notes.push(format!(
"Raw save fixed world block also exposes {} chairman slot selector bytes at file offset 0x{:x} and campaign_override_flag={} at file offset 0x{:x}.",
probe.chairman_slot_selectors.len(),
probe.chairman_slot_selector_offset,
probe.campaign_override_flag,
probe.campaign_override_flag_offset
));
if report.save_company_roster_probe.is_none()
|| report.save_chairman_profile_table_probe.is_none()
{
notes.push(
"Raw save inspection still does not reconstruct every company_roster or chairman_profile_table scalar lane; the grounded package-save path prefers direct-record reconstruction where it can and falls back to selection/header-only context otherwise."
.to_string(),
);
}
}
if let Some(probe) = &report.save_world_issue_37_probe {
notes.push(format!(
"Raw save fixed world block also exposes the grounded issue-0x37 pair: value={} at file offset 0x{:x} and companion multiplier {:.6} at file offset 0x{:x}.",
probe.issue_value_lane.value_i32,
probe.payload_offset + probe.issue_value_lane.relative_offset,
probe.multiplier_lane.value_f32,
probe.payload_offset + probe.multiplier_lane.relative_offset
));
}
if let Some(probe) = &report.save_world_economic_tuning_probe {
notes.push(format!(
"Raw save fixed world block also exposes the six-lane economic tuning float band at file offset 0x{:x} (mirror lane at 0x{:x}).",
probe.tuning_lanes
.first()
.map(|lane| probe.payload_offset + lane.relative_offset)
.unwrap_or(probe.payload_offset),
probe.payload_offset + probe.mirror_lane.relative_offset
));
notes.push(
"Current atlas evidence treats that fixed six-float world tuning band as the editor economic-cost family, not as the company-governance issue table behind investor confidence."
.to_string(),
);
}
if let Some(probe) = &report.save_company_collection_header_probe {
notes.push(format!(
"Raw save tagged company header reports live_record_count={} and live_id_bound={} at file offsets 0x{:x}/0x{:x}/0x{:x}.",
probe.live_record_count,
probe.live_id_bound,
probe.metadata_tag_offset,
probe.records_tag_offset,
probe.close_tag_offset
));
}
if let Some(probe) = &report.save_chairman_profile_collection_header_probe {
notes.push(format!(
"Raw save tagged chairman/profile header reports live_record_count={} and live_id_bound={} at file offsets 0x{:x}/0x{:x}/0x{:x}.",
probe.live_record_count,
probe.live_id_bound,
probe.metadata_tag_offset,
probe.records_tag_offset,
probe.close_tag_offset
));
}
if let Some(probe) = &report.save_region_collection_header_probe {
notes.push(format!(
"Raw save tagged region header reports live_record_count={} and live_id_bound={} with direct_record_stride=0x{:x} at file offsets 0x{:x}/0x{:x}/0x{:x}.",
probe.live_record_count,
probe.live_id_bound,
probe.direct_record_stride,
probe.metadata_tag_offset,
probe.records_tag_offset,
probe.close_tag_offset
));
}
if let Some(probe) = &report.save_region_collection_directory_probe {
notes.push(format!(
"Raw save tagged region metadata also exposes a live-entry directory with {} entries rooted at metadata dword {} (head={:?}, tail={:?}).",
probe.entries.len(),
probe.directory_root_dword_index,
probe.chain_head_live_entry_id,
probe.chain_tail_live_entry_id
));
}
if let Some(probe) = &report.save_placed_structure_collection_header_probe {
notes.push(format!(
"Raw save tagged placed-structure header reports live_record_count={} and live_id_bound={} with serialized stride hint 0x{:x} at file offsets 0x{:x}/0x{:x}/0x{:x}.",
probe.live_record_count,
probe.live_id_bound,
probe.direct_record_stride,
probe.metadata_tag_offset,
probe.records_tag_offset,
probe.close_tag_offset
));
}
if let Some(roster) = &report.save_company_roster_probe {
notes.push(format!(
"Raw save inspection reconstructed {} company direct records from the tagged company collection.",
roster.entries.len()
));
}
if let Some(table) = &report.save_chairman_profile_table_probe {
notes.push(format!(
"Raw save inspection reconstructed {} chairman/profile direct records from the tagged chairman collection.",
table.entries.len()
));
}
Ok(SmpLoadedSaveSlice {
file_extension_hint: summary.file_extension_hint.clone(),
container_profile_family: summary.container_profile_family.clone(),
mechanism_family: summary.mechanism_family.clone(),
mechanism_confidence: summary.mechanism_confidence.clone(),
trailer_family: summary.trailer_family.clone(),
bridge_family: summary.bridge_family.clone(),
profile,
candidate_availability_table,
named_locomotive_availability_table,
locomotive_catalog,
cargo_catalog,
world_issue_37_state,
world_economic_tuning_state,
world_finance_neighborhood_state,
world_locomotive_policy_state,
company_roster,
chairman_profile_table,
special_conditions_table,
event_runtime_collection: report.event_runtime_collection_summary.clone(),
notes,
})
}
pub fn inspect_save_company_and_chairman_analysis_file(
path: &Path,
) -> Result<SmpSaveCompanyChairmanAnalysisReport, Box<dyn std::error::Error>> {
let bytes = fs::read(path)?;
let report = inspect_bundle_bytes(
&bytes,
path.extension()
.and_then(|extension| extension.to_str())
.map(|extension| extension.to_ascii_lowercase()),
);
inspect_save_company_and_chairman_analysis_bytes(&bytes, &report)
.ok_or_else(|| "save inspection did not expose grounded company/chairman analysis".into())
}
pub fn inspect_save_company_and_chairman_analysis_bytes(
bytes: &[u8],
report: &SmpInspectionReport,
) -> Option<SmpSaveCompanyChairmanAnalysisReport> {
let selection_probe = report.save_world_selection_context_probe.as_ref();
let world_selection_context = selection_probe.map(build_save_world_selection_role_analysis);
let world_issue_37 = report.save_world_issue_37_probe.clone();
let world_economic_tuning = report.save_world_economic_tuning_probe.clone();
let world_finance_neighborhood = report.save_world_finance_neighborhood_probe.clone();
let region_collection_directory = report.save_region_collection_directory_probe.clone();
let company_header_probe = report.save_company_collection_header_probe.as_ref();
let chairman_header_probe = report
.save_chairman_profile_collection_header_probe
.as_ref();
let company_entries = if let Some(header_probe) = company_header_probe {
let record_start_offset = detect_save_company_record_start_offset(&bytes, header_probe)?;
let record_stride = header_probe.direct_record_stride as usize;
let base_offset = header_probe
.metadata_tag_offset
.checked_add(4)?
.checked_add(record_start_offset)?;
let mut entries = Vec::with_capacity(header_probe.live_record_count as usize);
for index in 0..header_probe.live_record_count as usize {
let record_offset = base_offset.checked_add(index.checked_mul(record_stride)?)?;
let company_id = read_u32_at(&bytes, record_offset)?;
let name = read_ascii_c_string_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_NAME_OFFSET,
SAVE_COMPANY_RECORD_NAME_MAX_LEN,
)?;
let active =
read_u8_at(&bytes, record_offset + SAVE_COMPANY_RECORD_ACTIVE_OFFSET)? != 0;
let linked_chairman_profile_id = parse_nonzero_u32(
&bytes,
record_offset + SAVE_COMPANY_RECORD_LINKED_CHAIRMAN_OFFSET,
)?;
let outstanding_shares = read_u32_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_OUTSTANDING_SHARES_OFFSET,
)?;
let debt = parse_save_company_total_debt(&bytes, record_offset)?;
let bond_count = read_u8_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_BOND_COUNT_OFFSET,
)?;
let live_bond_slots = parse_save_company_live_bond_slots(&bytes, record_offset)?;
let largest_live_bond_principal =
parse_save_company_largest_live_bond_principal(&bytes, record_offset)?;
let highest_coupon_live_bond_principal =
parse_save_company_highest_coupon_live_bond_principal(&bytes, record_offset)?;
let available_track_laying_capacity =
parse_save_company_available_track_laying_capacity(&bytes, record_offset)?;
let company_value_scalar_f32 = read_f32_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_COMPANY_VALUE_OFFSET,
)?;
let cached_share_support_scalar_f32 = read_f32_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_SUPPORT_SCALAR_OFFSET,
)?;
let cached_share_price_f32 = read_f32_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_CACHED_SHARE_PRICE_OFFSET,
)?;
let chairman_salary_baseline = read_u32_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_BASELINE_OFFSET,
)?;
let chairman_salary_current = read_u32_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_CURRENT_OFFSET,
)?;
let chairman_bonus_year = read_u32_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_YEAR_OFFSET,
)?;
let chairman_bonus_amount = read_i32_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_AMOUNT_OFFSET,
)?;
let founding_year = read_u32_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_FOUNDING_YEAR_OFFSET,
)?;
let last_bankruptcy_year = read_u32_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_LAST_BANKRUPTCY_YEAR_OFFSET,
)?;
let last_dividend_year = read_u32_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_LAST_DIVIDEND_YEAR_OFFSET,
)?;
let preferred_locomotive_engine_type_raw_u8 = read_u8_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_PREFERRED_LOCOMOTIVE_ENGINE_TYPE_OFFSET,
)?;
let city_connection_latch = read_u8_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_CITY_CONNECTION_LATCH_OFFSET,
)? != 0;
let linked_transit_latch = read_u8_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_LINKED_TRANSIT_LATCH_OFFSET,
)? != 0;
let merger_cooldown_year = read_u32_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_MERGER_COOLDOWN_OFFSET,
)?;
let takeover_cooldown_year = read_u32_at(
&bytes,
record_offset + SAVE_COMPANY_RECORD_TAKEOVER_COOLDOWN_OFFSET,
)?;
let scalar_dword_candidates = SAVE_COMPANY_RECORD_SCALAR_CANDIDATE_FIELDS
.iter()
.map(|(label, relative_offset)| {
build_save_dword_candidate(&bytes, record_offset, label, *relative_offset)
})
.collect::<Option<Vec<_>>>()?;
let post_capacity_dword_candidates = SAVE_COMPANY_RECORD_POST_CAPACITY_CANDIDATE_FIELDS
.iter()
.map(|(label, relative_offset)| {
build_save_dword_candidate(&bytes, record_offset, label, *relative_offset)
})
.collect::<Option<Vec<_>>>()?;
let stat_band_root_0cfb_candidates = build_save_company_stat_band_candidates(
&bytes,
record_offset,
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET,
"stat_band_0cfb",
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS,
)?;
let stat_band_root_0d7f_candidates = build_save_company_stat_band_candidates(
&bytes,
record_offset,
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET,
"stat_band_0d7f",
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS,
)?;
let stat_band_root_1c47_candidates = build_save_company_stat_band_candidates(
&bytes,
record_offset,
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET,
"stat_band_1c47",
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS,
)?;
entries.push(SmpSaveCompanyRecordAnalysisEntry {
company_id,
name,
active,
linked_chairman_profile_id,
outstanding_shares,
debt,
bond_count,
live_bond_slots,
largest_live_bond_principal,
highest_coupon_live_bond_principal,
available_track_laying_capacity,
company_value_scalar_f32,
cached_share_support_scalar_f32,
cached_share_price_f32,
chairman_salary_baseline,
chairman_salary_current,
chairman_bonus_year,
chairman_bonus_amount,
founding_year,
last_bankruptcy_year,
last_dividend_year,
preferred_locomotive_engine_type_raw_u8,
preferred_locomotive_engine_type_raw_hex: format!(
"0x{preferred_locomotive_engine_type_raw_u8:02x}"
),
city_connection_latch,
linked_transit_latch,
merger_cooldown_year,
takeover_cooldown_year,
scalar_dword_candidates,
post_capacity_dword_candidates,
stat_band_root_0cfb_candidates,
stat_band_root_0d7f_candidates,
stat_band_root_1c47_candidates,
});
}
entries
} else {
Vec::new()
};
let company_share_prices = company_entries
.iter()
.filter_map(|entry| {
round_f64_to_i64(entry.cached_share_price_f32 as f64)
.map(|share_price| (entry.company_id, share_price))
})
.collect::<BTreeMap<_, _>>();
let chairman_entries = if let Some(header_probe) = chairman_header_probe {
let record_start_offset =
detect_save_chairman_profile_record_start_offset(&bytes, header_probe)?;
let record_stride = header_probe.direct_record_stride as usize;
let base_offset = header_probe
.metadata_tag_offset
.checked_add(4)?
.checked_add(record_start_offset)?;
let company_id_bound = company_header_probe
.map(|probe| probe.live_id_bound)
.unwrap_or(0);
let mut entries = Vec::with_capacity(header_probe.live_record_count as usize);
for index in 0..header_probe.live_record_count as usize {
let record_offset = base_offset.checked_add(index.checked_mul(record_stride)?)?;
let profile_id = read_u32_at(&bytes, record_offset)?;
let active = read_u32_at(&bytes, record_offset + 4)? != 0;
let name = read_ascii_c_string_at(
&bytes,
record_offset + SAVE_CHAIRMAN_RECORD_NAME_OFFSET,
SAVE_CHAIRMAN_RECORD_NAME_MAX_LEN,
)?;
let current_cash =
read_f64_at(&bytes, record_offset + SAVE_CHAIRMAN_RECORD_CASH_OFFSET)?;
let linked_company_id = parse_nonzero_u32(
&bytes,
record_offset + SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET,
)?;
let personality_byte_0x291 = read_u8_at(
&bytes,
record_offset + SAVE_CHAIRMAN_RECORD_PERSONALITY_BYTE_0X291_OFFSET,
)?;
let mut holdings_by_company = BTreeMap::new();
for company_id in 1..=company_id_bound {
let slot_offset = record_offset
.checked_add(SAVE_CHAIRMAN_RECORD_HOLDINGS_BASE_OFFSET)?
.checked_add((company_id as usize).checked_mul(4)?)?;
let units = read_u32_at(&bytes, slot_offset)?;
if units != 0 {
holdings_by_company.insert(company_id, units);
}
}
let cached_scalar_candidates = SAVE_CHAIRMAN_RECORD_CACHE_CANDIDATE_OFFSETS
.iter()
.map(|relative_offset| {
build_save_qword_candidate(&bytes, record_offset, *relative_offset)
})
.collect::<Option<Vec<_>>>()?;
let rounded_current_cash = round_f64_to_i64(current_cash)?;
let derived_holdings_share_price_total = derive_chairman_holdings_share_price_total(
&holdings_by_company,
&company_share_prices,
);
let derived_net_worth_share_price_total = derived_holdings_share_price_total
.and_then(|holdings_total| rounded_current_cash.checked_add(holdings_total));
let derived_cached_purchasing_power_total =
derive_chairman_cached_purchasing_power_total(
rounded_current_cash,
&cached_scalar_candidates,
);
entries.push(SmpSaveChairmanRecordAnalysisEntry {
profile_id,
name,
active,
current_cash,
linked_company_id,
holdings_by_company,
derived_holdings_share_price_total,
derived_net_worth_share_price_total,
derived_cached_purchasing_power_total,
personality_byte_0x291,
personality_byte_0x291_hex: format!("0x{personality_byte_0x291:02x}"),
cached_scalar_candidates,
});
}
entries
} else {
Vec::new()
};
let mut notes = Vec::new();
if world_selection_context.is_some() {
notes.push(
"World selection context now exports the grounded chairman-slot selector bytes and per-slot role-gate bytes from the fixed save-side 0x32c8 world block."
.to_string(),
);
}
if world_issue_37.is_some() {
notes.push(
"World analysis now also exports the grounded issue-0x37 pair from the same 0x32c8 world payload: the clamped small issue value at [world+0x2d] and its companion multiplier lane at [world+0x29]."
.to_string(),
);
}
if world_economic_tuning.is_some() {
notes.push(
"World analysis now also exports the fixed six-lane economic tuning float block from the same 0x32c8 world payload; current atlas evidence still treats that band as distinct from the issue-0x37 investor-confidence family."
.to_string(),
);
}
if world_finance_neighborhood.is_some() {
notes.push(
"World analysis now also exports one fixed dword finance neighborhood around the grounded issue/calendar lanes, so future issue-0x38/0x39 closure can build on rehosted owner-state candidates instead of ad hoc byte guesses."
.to_string(),
);
}
if let Some(header) = report.save_region_collection_header_probe.as_ref() {
notes.push(format!(
"Region analysis now also exports the tagged region collection header: live_record_count={} live_id_bound={} direct_record_stride=0x{:x}.",
header.live_record_count, header.live_id_bound, header.direct_record_stride
));
}
if let Some(directory) = region_collection_directory.as_ref() {
notes.push(format!(
"Region analysis now also exports the tagged live-entry directory rooted at metadata dword {}: {} entries chained from {:?} to {:?}.",
directory.directory_root_dword_index,
directory.entries.len(),
directory.chain_head_live_entry_id,
directory.chain_tail_live_entry_id
));
}
if let Some(header) = report
.save_placed_structure_collection_header_probe
.as_ref()
{
notes.push(format!(
"Placed-structure analysis now also exports the tagged collection header: live_record_count={} live_id_bound={} serialized_stride_hint=0x{:x}.",
header.live_record_count, header.live_id_bound, header.direct_record_stride
));
}
if !company_entries.is_empty() {
notes.push(
"Company debt is derived from the grounded bond table at [company+0x5b/+0x5f] by summing live principal slots.".to_string(),
);
notes.push(
"Company available_track_laying_capacity is derived from the grounded tail dword [company+0x7680], with negative values treated as the unlimited sentinel.".to_string(),
);
notes.push(
"Company scalar_dword_candidates expose the current checked-in raw save windows around support/share-price/calendar lanes, and post_capacity_dword_candidates expose the immediate dwords after [company+0x7680] for deeper track-count and record-tail analysis.".to_string(),
);
notes.push(
"Company stat-band root candidates now also expose the first dword windows rooted at [company+0x0cfb], [company+0x0d7f], and [company+0x1c47], the same broader stat bands the grounded cheat reset branch clears before later finance/detail readers rebuild them.".to_string(),
);
notes.push(
"Current atlas evidence ties company current_cash and book_value_per_share to stat-family 0x2329 slots 0x0d and 0x1d, so the remaining save-native company finance/governance closure likely needs a structured company-stat family reconstruction instead of more isolated raw offsets."
.to_string(),
);
}
if !chairman_entries.is_empty() {
notes.push(
"Chairman cached_scalar_candidates expose the adjacent qword band rooted at [profile+0x1e9], now including raw qword hex and signed/f64 views for further purchasing-power analysis.".to_string(),
);
notes.push(
"Chairman analysis now also derives one holdings-at-cached-share-price total from the grounded company cached_share_price lane and one strongest-cached purchasing-power total from the nonnegative qword cache band."
.to_string(),
);
}
Some(SmpSaveCompanyChairmanAnalysisReport {
profile_family: report
.container_profile
.as_ref()
.map(|profile| profile.profile_family.clone())
.unwrap_or_else(|| "unknown".to_string()),
selected_company_id: selection_probe.map(|probe| probe.selected_company_id),
selected_chairman_profile_id: selection_probe
.map(|probe| probe.selected_chairman_profile_id),
world_selection_context,
world_issue_37,
world_economic_tuning,
world_finance_neighborhood,
region_collection_header: report.save_region_collection_header_probe.clone(),
region_collection_directory,
placed_structure_collection_header: report
.save_placed_structure_collection_header_probe
.clone(),
company_entries,
chairman_entries,
notes,
})
}
fn derive_locomotive_catalog_from_named_availability_table(
table: &SmpLoadedNamedLocomotiveAvailabilityTable,
) -> Option<SmpLoadedLocomotiveCatalog> {
if table.entries.is_empty() {
return None;
}
let entries = table
.entries
.iter()
.enumerate()
.map(|(index, entry)| SmpLoadedLocomotiveCatalogEntry {
locomotive_id: (index + 1) as u32,
name: entry.text.clone(),
})
.collect::<Vec<_>>();
Some(SmpLoadedLocomotiveCatalog {
source_kind: format!("{}-ordinal-catalog", table.source_kind),
semantic_family: "scenario-save-derived-locomotive-catalog".to_string(),
entries_offset: table.entries_offset,
observed_entry_count: entries.len(),
entries,
})
}
fn derive_cargo_catalog_from_recipe_book_probe(
probe: &SmpRecipeBookSummaryProbe,
) -> Option<SmpLoadedCargoCatalog> {
if probe.books.is_empty() {
return None;
}
let entries = probe
.books
.iter()
.filter(|book| book.book_index < 11)
.filter_map(|book| {
let line = book
.lines
.iter()
.find(|line| line.imports_to_runtime_descriptor)
.or_else(|| book.lines.first())?;
let slot_id = (book.book_index + 1) as u32;
let definition = known_cargo_slot_definition(slot_id)?;
Some(SmpLoadedCargoCatalogEntry {
slot_id,
label: definition.label.to_string(),
cargo_class: definition.cargo_class,
book_index: book.book_index,
max_annual_production_word: book.max_annual_production_word,
mode_word: line.mode_word,
runtime_import_branch_kind: line.runtime_import_branch_kind.clone(),
annual_amount_word: line.annual_amount_word,
supplied_cargo_token_word: line.supplied_cargo_token_word,
supplied_cargo_token_probable_high16_ascii_stem: line
.supplied_cargo_token_probable_high16_ascii_stem
.clone(),
demanded_cargo_token_word: line.demanded_cargo_token_word,
demanded_cargo_token_probable_high16_ascii_stem: line
.demanded_cargo_token_probable_high16_ascii_stem
.clone(),
})
})
.collect::<Vec<_>>();
if entries.is_empty() {
return None;
}
Some(SmpLoadedCargoCatalog {
source_kind: format!("{}-slot-catalog", probe.source_kind),
semantic_family: "scenario-save-derived-cargo-catalog".to_string(),
root_offset: Some(probe.root_offset),
observed_entry_count: entries.len(),
entries,
})
}
fn derive_loaded_world_issue_37_state_from_probe(
probe: &SmpSaveWorldIssue37Probe,
) -> SmpLoadedWorldIssue37State {
SmpLoadedWorldIssue37State {
source_kind: probe.source_kind.clone(),
semantic_family: probe.semantic_family.clone(),
issue_value: probe.issue_value_lane.raw_u32,
issue_value_hex: probe.issue_value_lane.raw_u32_hex.clone(),
issue_38_value: u32::from(probe.issue_38_raw_u8),
issue_38_value_hex: probe.issue_38_raw_hex.clone(),
issue_39_value: u32::from(probe.issue_39_raw_u8),
issue_39_value_hex: probe.issue_39_raw_hex.clone(),
issue_3a_value: u32::from(probe.issue_3a_raw_u8),
issue_3a_value_hex: probe.issue_3a_raw_hex.clone(),
multiplier_raw_u32: probe.multiplier_lane.raw_u32,
multiplier_raw_hex: probe.multiplier_lane.raw_u32_hex.clone(),
multiplier_value_f32_text: format!("{:.6}", probe.multiplier_lane.value_f32),
issue_opinion_base_terms_raw_i32: probe.issue_opinion_base_terms_raw_i32.clone(),
}
}
fn derive_loaded_world_economic_tuning_state_from_probe(
probe: &SmpSaveWorldEconomicTuningProbe,
) -> SmpLoadedWorldEconomicTuningState {
SmpLoadedWorldEconomicTuningState {
source_kind: probe.source_kind.clone(),
semantic_family: probe.semantic_family.clone(),
mirror_raw_u32: probe.mirror_lane.raw_u32,
mirror_raw_hex: probe.mirror_lane.raw_u32_hex.clone(),
mirror_value_f32_text: format!("{:.6}", probe.mirror_lane.value_f32),
lane_raw_u32: probe.tuning_lanes.iter().map(|lane| lane.raw_u32).collect(),
lane_raw_hex: probe
.tuning_lanes
.iter()
.map(|lane| lane.raw_u32_hex.clone())
.collect(),
lane_value_f32_text: probe
.tuning_lanes
.iter()
.map(|lane| format!("{:.6}", lane.value_f32))
.collect(),
}
}
fn derive_loaded_world_finance_neighborhood_state_from_probe(
probe: &SmpSaveWorldFinanceNeighborhoodProbe,
) -> SmpLoadedWorldFinanceNeighborhoodState {
SmpLoadedWorldFinanceNeighborhoodState {
source_kind: probe.source_kind.clone(),
semantic_family: probe.semantic_family.clone(),
packed_year_word_raw_u16: probe.packed_year_word_raw_u16,
packed_year_word_raw_hex: probe.packed_year_word_raw_hex.clone(),
partial_year_progress_raw_u8: probe.partial_year_progress_raw_u8,
partial_year_progress_raw_hex: probe.partial_year_progress_raw_hex.clone(),
current_calendar_tuple_word_raw_u32: probe.current_calendar_tuple_word_lane.raw_u32,
current_calendar_tuple_word_raw_hex: probe
.current_calendar_tuple_word_lane
.raw_u32_hex
.clone(),
current_calendar_tuple_word_2_raw_u32: probe.current_calendar_tuple_word_2_lane.raw_u32,
current_calendar_tuple_word_2_raw_hex: probe
.current_calendar_tuple_word_2_lane
.raw_u32_hex
.clone(),
absolute_counter_raw_u32: probe.absolute_counter_lane.raw_u32,
absolute_counter_raw_hex: probe.absolute_counter_lane.raw_u32_hex.clone(),
absolute_counter_mirror_raw_u32: probe.absolute_counter_mirror_lane.raw_u32,
absolute_counter_mirror_raw_hex: probe.absolute_counter_mirror_lane.raw_u32_hex.clone(),
stock_policy_raw_u8: probe.stock_policy_raw_u8,
stock_policy_raw_hex: probe.stock_policy_raw_hex.clone(),
bond_policy_raw_u8: probe.bond_policy_raw_u8,
bond_policy_raw_hex: probe.bond_policy_raw_hex.clone(),
bankruptcy_policy_raw_u8: probe.bankruptcy_policy_raw_u8,
bankruptcy_policy_raw_hex: probe.bankruptcy_policy_raw_hex.clone(),
dividend_policy_raw_u8: probe.dividend_policy_raw_u8,
dividend_policy_raw_hex: probe.dividend_policy_raw_hex.clone(),
building_density_growth_setting_raw_u32: probe.building_density_growth_setting_lane.raw_u32,
building_density_growth_setting_raw_hex: probe
.building_density_growth_setting_lane
.raw_u32_hex
.clone(),
labels: probe
.dword_candidates
.iter()
.map(|candidate| candidate.label.clone())
.collect(),
relative_offsets: probe
.dword_candidates
.iter()
.map(|candidate| candidate.relative_offset)
.collect(),
relative_offset_hex: probe
.dword_candidates
.iter()
.map(|candidate| candidate.relative_offset_hex.clone())
.collect(),
raw_u32: probe
.dword_candidates
.iter()
.map(|candidate| candidate.raw_u32)
.collect(),
raw_hex: probe
.dword_candidates
.iter()
.map(|candidate| candidate.raw_u32_hex.clone())
.collect(),
value_i32: probe
.dword_candidates
.iter()
.map(|candidate| candidate.value_i32)
.collect(),
value_f32_text: probe
.dword_candidates
.iter()
.map(|candidate| format!("{:.6}", candidate.value_f32))
.collect(),
}
}
fn derive_loaded_world_locomotive_policy_state_from_probes(
post_text_probe: Option<&SmpPostTextFieldNeighborhoodProbe>,
locomotive_policy_probe: Option<&SmpLocomotivePolicyNeighborhoodProbe>,
) -> Option<SmpLoadedWorldLocomotivePolicyState> {
let field_by_name = |name: &str| {
locomotive_policy_probe?
.grounded_field_observations
.iter()
.find(|field| field.field_name == name)
};
let post_text_field_by_name = |name: &str| {
post_text_probe?
.grounded_field_observations
.iter()
.find(|field| field.field_name == name)
};
let selected_year_gap_scalar = field_by_name("selected-year bucket companion scalar");
let linked_site_gate = field_by_name("linked-site removal follow-on gate");
let auto_show_grade = post_text_field_by_name("Auto-Show Grade During Track Lay");
let starting_building_density = post_text_field_by_name("Starting Building Density Level");
let building_density_growth = post_text_field_by_name("Building Density Growth");
let leftover_simulation_time = post_text_field_by_name("leftover simulation time accumulator");
let selected_year_snapshot = post_text_field_by_name("selected-year lane snapshot");
let all_steam = field_by_name("All Steam Locos Avail.");
let all_diesel = field_by_name("All Diesel Locos Avail.");
let all_electric = field_by_name("All Electric Locos Avail.");
let cached_available_rating = field_by_name("cached available-locomotive rating");
Some(SmpLoadedWorldLocomotivePolicyState {
source_kind: locomotive_policy_probe
.map(|probe| probe.source_kind.clone())
.or_else(|| post_text_probe.map(|probe| probe.source_kind.clone()))?,
semantic_family: "world-locomotive-policy".to_string(),
selected_year_gap_scalar_raw_u32: selected_year_gap_scalar
.and_then(|field| field.value_u32),
selected_year_gap_scalar_raw_hex: selected_year_gap_scalar
.and_then(|field| field.value_u32_hex.clone()),
selected_year_gap_scalar_value_f32_text: selected_year_gap_scalar
.and_then(|field| field.probable_f32_le.clone()),
linked_site_removal_follow_on_gate_raw_u8: linked_site_gate
.and_then(|field| field.value_u8),
linked_site_removal_follow_on_gate_raw_hex: linked_site_gate
.and_then(|field| field.value_u8_hex.clone()),
auto_show_grade_during_track_lay_raw_u8: auto_show_grade.and_then(|field| field.value_u8),
auto_show_grade_during_track_lay_raw_hex: auto_show_grade
.and_then(|field| field.value_u8_hex.clone()),
starting_building_density_level_raw_u8: starting_building_density
.and_then(|field| field.value_u8),
starting_building_density_level_raw_hex: starting_building_density
.and_then(|field| field.value_u8_hex.clone()),
building_density_growth_raw_u8: building_density_growth.and_then(|field| field.value_u8),
building_density_growth_raw_hex: building_density_growth
.and_then(|field| field.value_u8_hex.clone()),
leftover_simulation_time_accumulator_raw_u32: leftover_simulation_time
.and_then(|field| field.value_u32),
leftover_simulation_time_accumulator_raw_hex: leftover_simulation_time
.and_then(|field| field.value_u32_hex.clone()),
leftover_simulation_time_accumulator_value_f32_text: leftover_simulation_time
.and_then(|field| field.probable_f32_le.clone()),
selected_year_lane_snapshot_raw_u8: selected_year_snapshot.and_then(|field| field.value_u8),
selected_year_lane_snapshot_raw_hex: selected_year_snapshot
.and_then(|field| field.value_u8_hex.clone()),
all_steam_locomotives_available_raw_u8: all_steam.and_then(|field| field.value_u8),
all_steam_locomotives_available_raw_hex: all_steam
.and_then(|field| field.value_u8_hex.clone()),
all_diesel_locomotives_available_raw_u8: all_diesel.and_then(|field| field.value_u8),
all_diesel_locomotives_available_raw_hex: all_diesel
.and_then(|field| field.value_u8_hex.clone()),
all_electric_locomotives_available_raw_u8: all_electric.and_then(|field| field.value_u8),
all_electric_locomotives_available_raw_hex: all_electric
.and_then(|field| field.value_u8_hex.clone()),
cached_available_locomotive_rating_raw_u32: cached_available_rating
.and_then(|field| field.value_u32),
cached_available_locomotive_rating_raw_hex: cached_available_rating
.and_then(|field| field.value_u32_hex.clone()),
cached_available_locomotive_rating_value_f32_text: cached_available_rating
.and_then(|field| field.probable_f32_le.clone()),
})
}
fn derive_selection_only_company_roster_from_save_world_probe(
probe: &SmpSaveWorldSelectionContextProbe,
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
) -> Option<SmpLoadedCompanyRoster> {
Some(SmpLoadedCompanyRoster {
source_kind: format!("{}-company-selection-only", probe.source_kind),
semantic_family: "scenario-selected-company-context".to_string(),
observed_entry_count: header_probe
.map(|probe| probe.live_record_count as usize)
.unwrap_or(0),
selected_company_id: Some(probe.selected_company_id),
entries: Vec::new(),
})
}
fn derive_selection_only_chairman_profile_table_from_save_world_probe(
probe: &SmpSaveWorldSelectionContextProbe,
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
) -> Option<SmpLoadedChairmanProfileTable> {
Some(SmpLoadedChairmanProfileTable {
source_kind: format!("{}-chairman-selection-only", probe.source_kind),
semantic_family: "scenario-selected-chairman-context".to_string(),
observed_entry_count: header_probe
.map(|probe| probe.live_record_count as usize)
.unwrap_or(0),
selected_chairman_profile_id: Some(probe.selected_chairman_profile_id),
entries: Vec::new(),
})
}
const SAVE_COMPANY_RECORD_NAME_OFFSET: usize = 0x04;
const SAVE_COMPANY_RECORD_NAME_MAX_LEN: usize = 0x24;
const SAVE_COMPANY_RECORD_LINKED_CHAIRMAN_OFFSET: usize = 0x3b;
const SAVE_COMPANY_RECORD_ACTIVE_OFFSET: usize = 0x3f;
const SAVE_COMPANY_RECORD_OUTSTANDING_SHARES_OFFSET: usize = 0x47;
const SAVE_COMPANY_RECORD_SUPPORT_SCALAR_OFFSET: usize = 0x4f;
const SAVE_COMPANY_RECORD_COMPANY_VALUE_OFFSET: usize = 0x57;
const SAVE_COMPANY_RECORD_BOND_COUNT_OFFSET: usize = 0x5b;
const SAVE_COMPANY_RECORD_BOND_TABLE_OFFSET: usize = 0x5f;
const SAVE_COMPANY_RECORD_BOND_SLOT_STRIDE: usize = 12;
const SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_BASELINE_OFFSET: usize = 0x14f;
const SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_YEAR_OFFSET: usize = 0x34f;
const SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_AMOUNT_OFFSET: usize = 0x353;
const SAVE_COMPANY_RECORD_MERGER_COOLDOWN_OFFSET: usize = 0x15f;
const SAVE_COMPANY_RECORD_FOUNDING_YEAR_OFFSET: usize = 0x157;
const SAVE_COMPANY_RECORD_LAST_BANKRUPTCY_YEAR_OFFSET: usize = 0x163;
const SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET: usize = 0x16b;
const SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET_2: usize = 0x16f;
const SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET: usize = 0x173;
const SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET_2: usize = 0x177;
const SAVE_COMPANY_RECORD_TAKEOVER_COOLDOWN_OFFSET: usize = 0x289;
const SAVE_COMPANY_RECORD_LAST_DIVIDEND_YEAR_OFFSET: usize = 0x0d2d;
const SAVE_COMPANY_RECORD_PREFERRED_LOCOMOTIVE_ENGINE_TYPE_OFFSET: usize = 0x0d17;
const SAVE_COMPANY_RECORD_CITY_CONNECTION_LATCH_OFFSET: usize = 0x0d18;
const SAVE_COMPANY_RECORD_SUPPORT_PROGRESS_OFFSET: usize = 0x0d07;
const SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_CURRENT_OFFSET: usize = 0x0d59;
const SAVE_COMPANY_RECORD_LINKED_TRANSIT_LATCH_OFFSET: usize = 0x0d56;
const SAVE_COMPANY_RECORD_RECENT_PER_SHARE_SUBSCORE_OFFSET: usize = 0x0d19;
const SAVE_COMPANY_RECORD_CACHED_SHARE_PRICE_OFFSET: usize = 0x0d7b;
const SAVE_COMPANY_RECORD_TRACK_LAYING_CAPACITY_OFFSET: usize = 0x7680;
const SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET: usize = 0x0cfb;
const SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET: usize = 0x0d7f;
const SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET: usize = 0x1c47;
const SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS: usize = 32;
const SAVE_COMPANY_RECORD_ISSUE_OPINION_TERMS_OFFSET: usize = 0x2ab;
const SAVE_COMPANY_RECORD_ISSUE_OPINION_TERM_COUNT: usize =
RT3_SAVE_WORLD_BLOCK_ISSUE_OPINION_TERM_COUNT;
const SAVE_COMPANY_RECORD_YEAR_STAT_FAMILY_QWORD_COUNT: usize =
crate::runtime::RUNTIME_COMPANY_STAT_SLOT_COUNT as usize
* crate::runtime::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN as usize;
const SAVE_COMPANY_RECORD_SPECIAL_STAT_FAMILY_232A_QWORD_COUNT: usize =
crate::runtime::RUNTIME_COMPANY_STAT_SLOT_COUNT as usize;
const SAVE_COMPANY_RECORD_DIRECT_CONTROL_TRANSFER_FLOAT_FIELDS: [usize; 10] = [
0x4b, 0x53, 0x323, 0x327, 0x32b, 0x32f, 0x333, 0x337, 0x33b, 0x33f,
];
const SAVE_COMPANY_RECORD_DIRECT_CONTROL_TRANSFER_INT_FIELDS: [usize; 5] =
[0x14f, 0x34b, 0x0d0b, 0x0d0f, 0x0d13];
const SAVE_COMPANY_RECORD_SCALAR_CANDIDATE_FIELDS: [(&str, usize); 9] = [
("mutable_support_scalar", 0x4f),
("young_company_support_scalar", 0x57),
(
"support_progress_word",
SAVE_COMPANY_RECORD_SUPPORT_PROGRESS_OFFSET,
),
(
"recent_per_share_subscore",
SAVE_COMPANY_RECORD_RECENT_PER_SHARE_SUBSCORE_OFFSET,
),
("cached_share_price", 0x0d7b),
(
"current_issue_calendar_word",
SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET,
),
(
"current_issue_calendar_word_2",
SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET_2,
),
(
"prior_issue_calendar_word",
SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET,
),
(
"prior_issue_calendar_word_2",
SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET_2,
),
];
const SAVE_COMPANY_RECORD_POST_CAPACITY_CANDIDATE_FIELDS: [(&str, usize); 6] = [
("post_capacity_word_1", 0x7684),
("post_capacity_word_2", 0x7688),
("post_capacity_word_3", 0x768c),
("post_capacity_word_4", 0x7690),
("post_capacity_word_5", 0x7694),
("post_capacity_word_6", 0x7698),
];
const SAVE_COMPANY_RECORD_START_SCAN_LIMIT: usize = 0x120;
const SAVE_CHAIRMAN_RECORD_NAME_OFFSET: usize = 0x08;
const SAVE_CHAIRMAN_RECORD_NAME_MAX_LEN: usize = 0x1f;
const SAVE_CHAIRMAN_RECORD_CASH_OFFSET: usize = 0x154;
const SAVE_CHAIRMAN_RECORD_HOLDINGS_BASE_OFFSET: usize = 0x15d;
const SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET: usize = 0x1dd;
const SAVE_CHAIRMAN_RECORD_CACHE_0_OFFSET: usize = 0x1e9;
const SAVE_CHAIRMAN_RECORD_CACHE_1_OFFSET: usize = 0x1f1;
const SAVE_CHAIRMAN_RECORD_PERSONALITY_BYTE_0X291_OFFSET: usize = 0x291;
const SAVE_CHAIRMAN_RECORD_ISSUE_OPINION_TERMS_OFFSET: usize = 0x35b;
const SAVE_CHAIRMAN_RECORD_ISSUE_OPINION_TERM_COUNT: usize =
RT3_SAVE_WORLD_BLOCK_ISSUE_OPINION_TERM_COUNT;
const SAVE_CHAIRMAN_RECORD_CACHE_CANDIDATE_OFFSETS: [usize; 7] =
[0x1e9, 0x1f1, 0x1f9, 0x201, 0x209, 0x211, 0x219];
const SAVE_CHAIRMAN_RECORD_START_SCAN_LIMIT: usize = 0x80;
fn parse_save_company_roster_probe(
bytes: &[u8],
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
selection_probe: Option<&SmpSaveWorldSelectionContextProbe>,
) -> Option<SmpLoadedCompanyRoster> {
let header_probe = header_probe?;
let observed_entry_count = header_probe.live_record_count as usize;
if observed_entry_count == 0 {
return Some(SmpLoadedCompanyRoster {
source_kind: "save-company-direct-records".to_string(),
semantic_family: "scenario-save-company-direct-records".to_string(),
observed_entry_count,
selected_company_id: selection_probe.map(|probe| probe.selected_company_id),
entries: Vec::new(),
});
}
let record_start_offset = detect_save_company_record_start_offset(bytes, header_probe)?;
let record_stride = header_probe.direct_record_stride as usize;
let base_offset = header_probe
.metadata_tag_offset
.checked_add(4)?
.checked_add(record_start_offset)?;
let mut entries = Vec::with_capacity(observed_entry_count);
for index in 0..observed_entry_count {
let record_offset = base_offset.checked_add(index.checked_mul(record_stride)?)?;
let company_id = read_u32_at(bytes, record_offset)?;
let active = read_u8_at(bytes, record_offset + SAVE_COMPANY_RECORD_ACTIVE_OFFSET)? != 0;
let linked_chairman_profile_id = parse_nonzero_u32(
bytes,
record_offset + SAVE_COMPANY_RECORD_LINKED_CHAIRMAN_OFFSET,
)?;
let outstanding_shares = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_OUTSTANDING_SHARES_OFFSET,
)?;
let debt = parse_save_company_total_debt(bytes, record_offset)?;
let bond_count = read_u8_at(bytes, record_offset + SAVE_COMPANY_RECORD_BOND_COUNT_OFFSET)?;
let live_bond_slots = parse_save_company_live_bond_slots(bytes, record_offset)?;
let largest_live_bond_principal =
parse_save_company_largest_live_bond_principal(bytes, record_offset)?;
let highest_coupon_live_bond_principal =
parse_save_company_highest_coupon_live_bond_principal(bytes, record_offset)?;
let available_track_laying_capacity =
parse_save_company_available_track_laying_capacity(bytes, record_offset)?;
let mutable_support_scalar_raw_u32 = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_SUPPORT_SCALAR_OFFSET,
)?;
let young_company_support_scalar_raw_u32 = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_COMPANY_VALUE_OFFSET,
)?;
let support_progress_word = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_SUPPORT_PROGRESS_OFFSET,
)?;
let recent_per_share_cache_absolute_counter = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET,
)?;
let recent_per_share_cached_value_bits = read_u64_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET + 4,
)?;
let recent_per_share_subscore_raw_u32 = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_RECENT_PER_SHARE_SUBSCORE_OFFSET,
)?;
let cached_share_price_raw_u32 = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_CACHED_SHARE_PRICE_OFFSET,
)?;
let chairman_salary_baseline = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_BASELINE_OFFSET,
)?;
let chairman_salary_current = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_CURRENT_OFFSET,
)?;
let chairman_bonus_year = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_YEAR_OFFSET,
)?;
let chairman_bonus_amount = read_i32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_AMOUNT_OFFSET,
)?;
let founding_year = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_FOUNDING_YEAR_OFFSET,
)?;
let last_bankruptcy_year = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_LAST_BANKRUPTCY_YEAR_OFFSET,
)?;
let last_dividend_year = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_LAST_DIVIDEND_YEAR_OFFSET,
)?;
let current_issue_calendar_word = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET,
)?;
let current_issue_calendar_word_2 = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET_2,
)?;
let prior_issue_calendar_word = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET,
)?;
let prior_issue_calendar_word_2 = read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET_2,
)?;
let preferred_locomotive_engine_type_raw_u8 = read_u8_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_PREFERRED_LOCOMOTIVE_ENGINE_TYPE_OFFSET,
)?;
let city_connection_latch = read_u8_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_CITY_CONNECTION_LATCH_OFFSET,
)? != 0;
let linked_transit_latch = read_u8_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_LINKED_TRANSIT_LATCH_OFFSET,
)? != 0;
let merger_cooldown_year = parse_nonzero_u32(
bytes,
record_offset + SAVE_COMPANY_RECORD_MERGER_COOLDOWN_OFFSET,
)?;
let takeover_cooldown_year = parse_nonzero_u32(
bytes,
record_offset + SAVE_COMPANY_RECORD_TAKEOVER_COOLDOWN_OFFSET,
)?;
let stat_band_root_0cfb_candidates = build_save_company_stat_band_candidates(
bytes,
record_offset,
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET,
"stat_band_0cfb",
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS,
)?;
let stat_band_root_0d7f_candidates = build_save_company_stat_band_candidates(
bytes,
record_offset,
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET,
"stat_band_0d7f",
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS,
)?;
let stat_band_root_1c47_candidates = build_save_company_stat_band_candidates(
bytes,
record_offset,
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET,
"stat_band_1c47",
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS,
)?;
let year_stat_family_qword_bits = build_save_company_stat_qword_bits(
bytes,
record_offset,
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET,
SAVE_COMPANY_RECORD_YEAR_STAT_FAMILY_QWORD_COUNT,
)?;
let special_stat_family_232a_qword_bits = build_save_company_stat_qword_bits(
bytes,
record_offset,
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET,
SAVE_COMPANY_RECORD_SPECIAL_STAT_FAMILY_232A_QWORD_COUNT,
)?;
let direct_control_transfer_float_fields_raw_u32 = build_save_u32_field_map(
bytes,
record_offset,
&SAVE_COMPANY_RECORD_DIRECT_CONTROL_TRANSFER_FLOAT_FIELDS,
)?;
let direct_control_transfer_int_fields_raw_u32 = build_save_u32_field_map(
bytes,
record_offset,
&SAVE_COMPANY_RECORD_DIRECT_CONTROL_TRANSFER_INT_FIELDS,
)?;
let issue_opinion_terms_raw_i32 = build_save_i32_term_strip(
bytes,
record_offset,
SAVE_COMPANY_RECORD_ISSUE_OPINION_TERMS_OFFSET,
SAVE_COMPANY_RECORD_ISSUE_OPINION_TERM_COUNT,
)?;
let current_cash = decode_save_company_current_year_stat_slot(
&year_stat_family_qword_bits,
crate::RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
)
.and_then(round_f64_to_i64)
.unwrap_or(0);
entries.push(SmpLoadedCompanyRosterEntry {
company_id,
active,
controller_kind: RuntimeCompanyControllerKind::Unknown,
current_cash,
debt,
credit_rating_score: None,
prime_rate: None,
available_track_laying_capacity,
track_piece_counts: RuntimeTrackPieceCounts::default(),
linked_chairman_profile_id,
book_value_per_share: 0,
investor_confidence: 0,
management_attitude: 0,
takeover_cooldown_year,
merger_cooldown_year,
preferred_locomotive_engine_type_raw_u8: Some(preferred_locomotive_engine_type_raw_u8),
market_state: Some(RuntimeCompanyMarketState {
outstanding_shares,
bond_count,
live_bond_slots,
largest_live_bond_principal,
highest_coupon_live_bond_principal,
mutable_support_scalar_raw_u32,
young_company_support_scalar_raw_u32,
support_progress_word,
recent_per_share_cache_absolute_counter,
recent_per_share_cached_value_bits,
recent_per_share_subscore_raw_u32,
cached_share_price_raw_u32,
chairman_salary_baseline,
chairman_salary_current,
chairman_bonus_year,
chairman_bonus_amount,
founding_year,
last_bankruptcy_year,
last_dividend_year,
current_issue_calendar_word,
current_issue_calendar_word_2,
prior_issue_calendar_word,
prior_issue_calendar_word_2,
city_connection_latch,
linked_transit_latch,
stat_band_root_0cfb_candidates: stat_band_root_0cfb_candidates
.iter()
.map(runtime_company_stat_band_candidate_from_save)
.collect(),
stat_band_root_0d7f_candidates: stat_band_root_0d7f_candidates
.iter()
.map(runtime_company_stat_band_candidate_from_save)
.collect(),
stat_band_root_1c47_candidates: stat_band_root_1c47_candidates
.iter()
.map(runtime_company_stat_band_candidate_from_save)
.collect(),
year_stat_family_qword_bits,
special_stat_family_232a_qword_bits,
issue_opinion_terms_raw_i32,
direct_control_transfer_float_fields_raw_u32,
direct_control_transfer_int_fields_raw_u32,
}),
});
}
Some(SmpLoadedCompanyRoster {
source_kind: "save-company-direct-records".to_string(),
semantic_family: "scenario-save-company-direct-records".to_string(),
observed_entry_count,
selected_company_id: selection_probe.map(|probe| probe.selected_company_id),
entries,
})
}
fn runtime_company_stat_band_candidate_from_save(
candidate: &SmpSaveDwordCandidate,
) -> crate::RuntimeCompanyStatBandCandidate {
crate::RuntimeCompanyStatBandCandidate {
label: candidate.label.clone(),
relative_offset: candidate.relative_offset,
relative_offset_hex: candidate.relative_offset_hex.clone(),
raw_u32: candidate.raw_u32,
raw_u32_hex: candidate.raw_u32_hex.clone(),
value_i32: candidate.value_i32,
value_f32_text: format!("{:.6}", candidate.value_f32),
}
}
fn build_save_company_stat_band_candidates(
bytes: &[u8],
record_offset: usize,
root_offset: usize,
label_prefix: &str,
word_count: usize,
) -> Option<Vec<SmpSaveDwordCandidate>> {
(0..word_count)
.map(|index| {
let relative_offset = root_offset.checked_add(index.checked_mul(4)?)?;
let label = format!("{label_prefix}_word_{}", index + 1);
build_save_dword_candidate(bytes, record_offset, &label, relative_offset)
})
.collect::<Option<Vec<_>>>()
}
fn build_save_company_stat_qword_bits(
bytes: &[u8],
record_offset: usize,
root_offset: usize,
qword_count: usize,
) -> Option<Vec<u64>> {
(0..qword_count)
.map(|index| {
let relative_offset = root_offset.checked_add(index.checked_mul(8)?)?;
read_u64_at(bytes, record_offset + relative_offset)
})
.collect::<Option<Vec<_>>>()
}
fn build_save_i32_term_strip(
bytes: &[u8],
record_offset: usize,
root_offset: usize,
value_count: usize,
) -> Option<Vec<i32>> {
(0..value_count)
.map(|index| {
let relative_offset = root_offset.checked_add(index.checked_mul(4)?)?;
read_i32_at(bytes, record_offset + relative_offset)
})
.collect::<Option<Vec<_>>>()
}
fn build_save_u32_field_map(
bytes: &[u8],
record_offset: usize,
offsets: &[usize],
) -> Option<BTreeMap<u32, u32>> {
let mut fields = BTreeMap::new();
for relative_offset in offsets {
fields.insert(
u32::try_from(*relative_offset).ok()?,
read_u32_at(bytes, record_offset + *relative_offset)?,
);
}
Some(fields)
}
fn decode_save_company_current_year_stat_slot(
year_stat_family_qword_bits: &[u64],
slot_id: u32,
) -> Option<f64> {
let index = slot_id.checked_mul(crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)? as usize;
let value = f64::from_bits(*year_stat_family_qword_bits.get(index)?);
value.is_finite().then_some(value)
}
fn detect_save_company_record_start_offset(
bytes: &[u8],
header_probe: &SmpSaveTaggedCollectionHeaderProbe,
) -> Option<usize> {
let observed_entry_count = header_probe.live_record_count as usize;
let record_stride = header_probe.direct_record_stride as usize;
let scan_limit = SAVE_COMPANY_RECORD_START_SCAN_LIMIT.min(record_stride);
let base_offset = header_probe.metadata_tag_offset.checked_add(4)?;
let mut best_start = None;
let mut best_score = 0usize;
for start in 0..scan_limit {
let mut score = 0usize;
let mut seen_ids = std::collections::BTreeSet::new();
let mut valid = true;
for index in 0..observed_entry_count {
let record_offset = match base_offset
.checked_add(start)
.and_then(|offset| offset.checked_add(index.checked_mul(record_stride)?))
{
Some(offset) => offset,
None => {
valid = false;
break;
}
};
let company_id = match read_u32_at(bytes, record_offset) {
Some(value)
if value >= 1
&& value <= header_probe.live_id_bound
&& seen_ids.insert(value) =>
{
value
}
_ => {
valid = false;
break;
}
};
let active = match read_u8_at(bytes, record_offset + SAVE_COMPANY_RECORD_ACTIVE_OFFSET)
{
Some(0 | 1) => {
score += 1;
true
}
_ => {
valid = false;
break;
}
};
let linked = match read_u32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_LINKED_CHAIRMAN_OFFSET,
) {
Some(value) if value <= 0x100 => value,
_ => {
valid = false;
break;
}
};
let name = match read_ascii_c_string_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_NAME_OFFSET,
SAVE_COMPANY_RECORD_NAME_MAX_LEN,
) {
Some(name) if !name.is_empty() && name.chars().all(is_save_name_char) => name,
_ => {
valid = false;
break;
}
};
score += name.len();
if active {
score += 8;
}
if linked != 0 {
score += 2;
}
if company_id == (index + 1) as u32 {
score += 4;
}
}
if valid && score > best_score {
best_score = score;
best_start = Some(start);
}
}
best_start
}
fn parse_save_company_total_debt(bytes: &[u8], record_offset: usize) -> Option<u64> {
let bond_count =
read_u8_at(bytes, record_offset + SAVE_COMPANY_RECORD_BOND_COUNT_OFFSET)? as usize;
let mut total = 0u64;
for slot_index in 0..bond_count {
let slot_offset = record_offset
.checked_add(SAVE_COMPANY_RECORD_BOND_TABLE_OFFSET)?
.checked_add(slot_index.checked_mul(SAVE_COMPANY_RECORD_BOND_SLOT_STRIDE)?)?;
let principal = read_i32_at(bytes, slot_offset)?;
if principal > 0 {
total = total.checked_add(principal as u64)?;
}
}
Some(total)
}
fn parse_save_company_live_bond_slots(
bytes: &[u8],
record_offset: usize,
) -> Option<Vec<crate::RuntimeCompanyBondSlot>> {
let bond_count =
read_u8_at(bytes, record_offset + SAVE_COMPANY_RECORD_BOND_COUNT_OFFSET)? as usize;
let mut slots = Vec::new();
for slot_index in 0..bond_count {
let slot_offset = record_offset
.checked_add(SAVE_COMPANY_RECORD_BOND_TABLE_OFFSET)?
.checked_add(slot_index.checked_mul(SAVE_COMPANY_RECORD_BOND_SLOT_STRIDE)?)?;
let principal = read_i32_at(bytes, slot_offset)?;
if principal <= 0 {
continue;
}
let maturity_year = read_u32_at(bytes, slot_offset + 4)?;
let coupon_rate_raw_u32 = read_u32_at(bytes, slot_offset + 8)?;
let coupon_rate = f32::from_bits(coupon_rate_raw_u32);
if !coupon_rate.is_finite() {
continue;
}
slots.push(crate::RuntimeCompanyBondSlot {
slot_index: slot_index as u32,
principal: principal as u32,
maturity_year,
coupon_rate_raw_u32,
});
}
Some(slots)
}
fn parse_save_company_largest_live_bond_principal(
bytes: &[u8],
record_offset: usize,
) -> Option<Option<u32>> {
let mut largest_live_principal: Option<u32> = None;
for slot in parse_save_company_live_bond_slots(bytes, record_offset)? {
largest_live_principal = Some(match largest_live_principal {
Some(current) => current.max(slot.principal),
None => slot.principal,
});
}
Some(largest_live_principal)
}
fn parse_save_company_highest_coupon_live_bond_principal(
bytes: &[u8],
record_offset: usize,
) -> Option<Option<u32>> {
let mut highest_coupon_principal = None;
let mut highest_coupon_rate = None;
for slot in parse_save_company_live_bond_slots(bytes, record_offset)? {
let coupon_rate = f32::from_bits(slot.coupon_rate_raw_u32);
match highest_coupon_rate {
Some(current_rate) if coupon_rate < current_rate => {}
Some(current_rate) if coupon_rate == current_rate => {
if let Some(current_principal) = highest_coupon_principal {
if slot.principal > current_principal {
highest_coupon_principal = Some(slot.principal);
}
}
}
_ => {
highest_coupon_rate = Some(coupon_rate);
highest_coupon_principal = Some(slot.principal);
}
}
}
Some(highest_coupon_principal)
}
fn parse_save_company_available_track_laying_capacity(
bytes: &[u8],
record_offset: usize,
) -> Option<Option<u32>> {
let raw = read_i32_at(
bytes,
record_offset + SAVE_COMPANY_RECORD_TRACK_LAYING_CAPACITY_OFFSET,
)?;
if raw < 0 {
Some(None)
} else {
Some(Some(raw as u32))
}
}
fn build_save_dword_candidate(
bytes: &[u8],
record_offset: usize,
label: &str,
relative_offset: usize,
) -> Option<SmpSaveDwordCandidate> {
let raw_u32 = read_u32_at(bytes, record_offset + relative_offset)?;
Some(SmpSaveDwordCandidate {
label: label.to_string(),
relative_offset,
relative_offset_hex: format!("0x{relative_offset:x}"),
raw_u32,
raw_u32_hex: format!("0x{raw_u32:08x}"),
value_i32: raw_u32 as i32,
value_f32: f32::from_bits(raw_u32),
})
}
fn build_save_qword_candidate(
bytes: &[u8],
record_offset: usize,
relative_offset: usize,
) -> Option<SmpSaveScalarCandidate> {
let raw_u64 = read_u64_at(bytes, record_offset + relative_offset)?;
Some(SmpSaveScalarCandidate {
relative_offset,
relative_offset_hex: format!("0x{relative_offset:x}"),
raw_u64,
raw_u64_hex: format!("0x{raw_u64:016x}"),
value_i64: raw_u64 as i64,
value_f64: f64::from_bits(raw_u64),
})
}
fn derive_chairman_holdings_share_price_total(
holdings_by_company: &BTreeMap<u32, u32>,
company_share_prices: &BTreeMap<u32, i64>,
) -> Option<i64> {
let mut total = 0i64;
for (company_id, units) in holdings_by_company {
let share_price = *company_share_prices.get(company_id)?;
total = total.checked_add((*units as i64).checked_mul(share_price)?)?;
}
Some(total)
}
fn derive_chairman_cached_purchasing_power_total(
current_cash: i64,
cached_scalar_candidates: &[SmpSaveScalarCandidate],
) -> Option<i64> {
let strongest_cached_total = cached_scalar_candidates
.iter()
.filter_map(|candidate| round_f64_to_i64(candidate.value_f64))
.filter(|value| *value >= 0)
.max()?;
current_cash.checked_add(strongest_cached_total)
}
fn build_save_world_selection_role_analysis(
probe: &SmpSaveWorldSelectionContextProbe,
) -> SmpSaveWorldSelectionRoleAnalysis {
let chairman_slots = probe
.chairman_slot_selectors
.iter()
.copied()
.zip(probe.chairman_role_gate_bytes.iter().copied())
.enumerate()
.map(|(slot_index, (selector_byte, role_gate_byte))| {
SmpSaveWorldSelectionRoleAnalysisEntry {
slot_index,
selector_byte,
selector_byte_hex: format!("0x{selector_byte:02x}"),
role_gate_byte,
role_gate_byte_hex: format!("0x{role_gate_byte:02x}"),
}
})
.collect();
SmpSaveWorldSelectionRoleAnalysis {
selected_company_id: probe.selected_company_id,
selected_chairman_profile_id: probe.selected_chairman_profile_id,
campaign_override_flag: probe.campaign_override_flag,
campaign_override_flag_hex: probe.campaign_override_flag_hex.clone(),
chairman_slots,
}
}
fn parse_save_chairman_profile_table_probe(
bytes: &[u8],
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
selection_probe: Option<&SmpSaveWorldSelectionContextProbe>,
company_header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
) -> Option<SmpLoadedChairmanProfileTable> {
let header_probe = header_probe?;
let observed_entry_count = header_probe.live_record_count as usize;
if observed_entry_count == 0 {
return Some(SmpLoadedChairmanProfileTable {
source_kind: "save-chairman-profile-direct-records".to_string(),
semantic_family: "scenario-save-chairman-profile-direct-records".to_string(),
observed_entry_count,
selected_chairman_profile_id: selection_probe
.map(|probe| probe.selected_chairman_profile_id),
entries: Vec::new(),
});
}
let record_start_offset =
detect_save_chairman_profile_record_start_offset(bytes, header_probe)?;
let record_stride = header_probe.direct_record_stride as usize;
let base_offset = header_probe
.metadata_tag_offset
.checked_add(4)?
.checked_add(record_start_offset)?;
let company_id_bound = company_header_probe
.map(|probe| probe.live_id_bound)
.unwrap_or(0);
let mut entries = Vec::with_capacity(observed_entry_count);
for index in 0..observed_entry_count {
let record_offset = base_offset.checked_add(index.checked_mul(record_stride)?)?;
let profile_id = read_u32_at(bytes, record_offset)?;
let active = read_u32_at(bytes, record_offset + 4)? != 0;
let name = read_ascii_c_string_at(
bytes,
record_offset + SAVE_CHAIRMAN_RECORD_NAME_OFFSET,
SAVE_CHAIRMAN_RECORD_NAME_MAX_LEN,
)?;
let current_cash = round_f64_to_i64(read_f64_at(
bytes,
record_offset + SAVE_CHAIRMAN_RECORD_CASH_OFFSET,
)?)?;
let linked_company_id = parse_nonzero_u32(
bytes,
record_offset + SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET,
)?;
let personality_byte_0x291 = read_u8_at(
bytes,
record_offset + SAVE_CHAIRMAN_RECORD_PERSONALITY_BYTE_0X291_OFFSET,
)?;
let cache_0 = round_f64_to_i64(read_f64_at(
bytes,
record_offset + SAVE_CHAIRMAN_RECORD_CACHE_0_OFFSET,
)?)?;
let cache_1 = round_f64_to_i64(read_f64_at(
bytes,
record_offset + SAVE_CHAIRMAN_RECORD_CACHE_1_OFFSET,
)?)?;
let cached_scalar_candidates = SAVE_CHAIRMAN_RECORD_CACHE_CANDIDATE_OFFSETS
.iter()
.map(|relative_offset| {
build_save_qword_candidate(bytes, record_offset, *relative_offset)
})
.collect::<Option<Vec<_>>>()?;
let issue_opinion_terms_raw_i32 = build_save_i32_term_strip(
bytes,
record_offset,
SAVE_CHAIRMAN_RECORD_ISSUE_OPINION_TERMS_OFFSET,
SAVE_CHAIRMAN_RECORD_ISSUE_OPINION_TERM_COUNT,
)?;
let holdings_value_total = cache_0.max(cache_1).max(0);
let net_worth_total = current_cash.saturating_add(holdings_value_total);
let purchasing_power_total =
derive_chairman_cached_purchasing_power_total(current_cash, &cached_scalar_candidates)
.unwrap_or(net_worth_total);
let mut company_holdings = BTreeMap::new();
for company_id in 1..=company_id_bound {
let slot_offset = record_offset
.checked_add(SAVE_CHAIRMAN_RECORD_HOLDINGS_BASE_OFFSET)?
.checked_add((company_id as usize).checked_mul(4)?)?;
let units = read_u32_at(bytes, slot_offset)?;
if units != 0 {
company_holdings.insert(company_id, units);
}
}
entries.push(SmpLoadedChairmanProfileEntry {
profile_id,
name,
active,
current_cash,
linked_company_id,
company_holdings,
holdings_value_total,
net_worth_total,
purchasing_power_total,
personality_byte_0x291: Some(personality_byte_0x291),
issue_opinion_terms_raw_i32,
});
}
Some(SmpLoadedChairmanProfileTable {
source_kind: "save-chairman-profile-direct-records".to_string(),
semantic_family: "scenario-save-chairman-profile-direct-records".to_string(),
observed_entry_count,
selected_chairman_profile_id: selection_probe
.map(|probe| probe.selected_chairman_profile_id),
entries,
})
}
fn detect_save_chairman_profile_record_start_offset(
bytes: &[u8],
header_probe: &SmpSaveTaggedCollectionHeaderProbe,
) -> Option<usize> {
let observed_entry_count = header_probe.live_record_count as usize;
let record_stride = header_probe.direct_record_stride as usize;
let scan_limit = SAVE_CHAIRMAN_RECORD_START_SCAN_LIMIT.min(record_stride);
let base_offset = header_probe.metadata_tag_offset.checked_add(4)?;
let mut best_start = None;
let mut best_score = 0usize;
for start in 0..scan_limit {
let mut score = 0usize;
let mut seen_ids = std::collections::BTreeSet::new();
let mut valid = true;
for index in 0..observed_entry_count {
let record_offset = match base_offset
.checked_add(start)
.and_then(|offset| offset.checked_add(index.checked_mul(record_stride)?))
{
Some(offset) => offset,
None => {
valid = false;
break;
}
};
let profile_id = match read_u32_at(bytes, record_offset) {
Some(value)
if value >= 1
&& value <= header_probe.live_id_bound
&& seen_ids.insert(value) =>
{
value
}
_ => {
valid = false;
break;
}
};
match read_u32_at(bytes, record_offset + 4) {
Some(0 | 1) => score += 1,
_ => {
valid = false;
break;
}
}
let name = match read_ascii_c_string_at(
bytes,
record_offset + SAVE_CHAIRMAN_RECORD_NAME_OFFSET,
SAVE_CHAIRMAN_RECORD_NAME_MAX_LEN,
) {
Some(name) if !name.is_empty() && name.chars().all(is_save_name_char) => name,
_ => {
valid = false;
break;
}
};
match read_u32_at(
bytes,
record_offset + SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET,
) {
Some(value) if value <= 0x100 => score += (value != 0) as usize,
_ => {
valid = false;
break;
}
}
match read_f64_at(bytes, record_offset + SAVE_CHAIRMAN_RECORD_CASH_OFFSET) {
Some(value) if value.is_finite() && value.abs() < 1.0e12 => {
score += name.len() + 4;
}
_ => {
valid = false;
break;
}
}
if profile_id == (index + 1) as u32 {
score += 4;
}
}
if valid && score > best_score {
best_score = score;
best_start = Some(start);
}
}
best_start
}
fn is_save_name_char(ch: char) -> bool {
ch.is_ascii_alphanumeric()
|| matches!(
ch,
' ' | '&' | '\'' | ',' | '.' | '-' | '/' | '(' | ')' | ':'
)
}
fn known_cargo_slot_definition(slot_id: u32) -> Option<KnownCargoSlotDefinition> {
KNOWN_CARGO_SLOT_DEFINITIONS
.iter()
.copied()
.find(|definition| definition.slot_id == slot_id)
}
fn known_cargo_slot_definition_for_descriptor_id(
descriptor_id: u32,
) -> Option<KnownCargoSlotDefinition> {
KNOWN_CARGO_SLOT_DEFINITIONS
.iter()
.copied()
.find(|definition| definition.descriptor_id == descriptor_id)
}
fn runtime_cargo_class_name(cargo_class: RuntimeCargoClass) -> &'static str {
match cargo_class {
RuntimeCargoClass::Factory => "factory",
RuntimeCargoClass::FarmMine => "farm_mine",
RuntimeCargoClass::Other => "other",
}
}
fn parse_event_runtime_collection_summary(
bytes: &[u8],
container_profile: Option<&SmpContainerProfile>,
save_load_summary: Option<&SmpSaveLoadSummary>,
) -> Option<SmpLoadedEventRuntimeCollectionSummary> {
let metadata_offsets = find_u16_le_offsets(bytes, EVENT_RUNTIME_COLLECTION_METADATA_TAG);
let record_offsets = find_u16_le_offsets(bytes, EVENT_RUNTIME_COLLECTION_RECORDS_TAG);
let close_offsets = find_u16_le_offsets(bytes, EVENT_RUNTIME_COLLECTION_CLOSE_TAG);
for metadata_tag_offset in metadata_offsets {
let packed_state_version = read_u32_at(bytes, metadata_tag_offset + 2)?;
if packed_state_version != EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION {
continue;
}
let records_tag_offset = record_offsets
.iter()
.copied()
.find(|offset| *offset > metadata_tag_offset + 6)?;
let close_tag_offset = close_offsets
.iter()
.copied()
.find(|offset| *offset > records_tag_offset)?;
let metadata_payload = bytes.get(metadata_tag_offset + 6..records_tag_offset)?;
if metadata_payload.len() < INDEXED_COLLECTION_SERIALIZED_HEADER_LEN {
continue;
}
let header_words = (0..INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT)
.map(|index| read_u32_at(metadata_payload, index * 4))
.collect::<Option<Vec<_>>>()?;
let direct_collection_flag = header_words[0];
let direct_record_stride = usize::try_from(header_words[1]).ok()?;
let live_id_bound = header_words[4];
let live_record_count = usize::try_from(header_words[5]).ok()?;
if direct_collection_flag == 0 || direct_record_stride == 0 {
continue;
}
let bitset_len = ((usize::try_from(live_id_bound).ok()?).saturating_add(15)) / 8;
let payload_bytes = direct_record_stride.checked_mul(live_record_count)?;
if metadata_payload.len() < INDEXED_COLLECTION_SERIALIZED_HEADER_LEN + bitset_len {
continue;
}
if metadata_payload.len() < bitset_len + payload_bytes {
continue;
}
let bitset_offset = metadata_payload.len() - bitset_len - payload_bytes;
if bitset_offset < INDEXED_COLLECTION_SERIALIZED_HEADER_LEN {
continue;
}
let bitset = metadata_payload.get(bitset_offset..bitset_offset + bitset_len)?;
let live_entry_ids = decode_live_entry_ids_from_tombstone_bitset(bitset, live_id_bound)?;
if live_entry_ids.len() != live_record_count {
continue;
}
let records_payload = bytes.get(records_tag_offset + 2..close_tag_offset)?;
let records = parse_event_runtime_record_summaries(
records_payload,
records_tag_offset + 2,
&live_entry_ids,
);
let decoded_record_count = records
.iter()
.filter(|record| record.decode_status != "unsupported_framing")
.count();
let imported_runtime_record_count = records
.iter()
.filter(|record| record.executable_import_ready)
.count();
return Some(SmpLoadedEventRuntimeCollectionSummary {
source_kind: "packed-event-runtime-collection".to_string(),
mechanism_family: save_load_summary
.map(|summary| summary.mechanism_family.clone())
.unwrap_or_else(|| "unknown".to_string()),
mechanism_confidence: save_load_summary
.map(|summary| summary.mechanism_confidence.clone())
.unwrap_or_else(|| "inferred".to_string()),
container_profile_family: container_profile
.map(|profile| profile.profile_family.clone()),
metadata_tag_offset,
records_tag_offset,
close_tag_offset,
packed_state_version,
packed_state_version_hex: format!("0x{packed_state_version:08x}"),
live_id_bound,
live_record_count,
live_entry_ids,
decoded_record_count,
imported_runtime_record_count,
records,
});
}
None
}
fn decode_live_entry_ids_from_tombstone_bitset(
bitset: &[u8],
live_id_bound: u32,
) -> Option<Vec<u32>> {
let ids = decode_live_entry_ids_with_mapping(bitset, live_id_bound, false);
if ids.is_some() {
return ids;
}
decode_live_entry_ids_with_mapping(bitset, live_id_bound, true)
}
fn decode_live_entry_ids_with_mapping(
bitset: &[u8],
live_id_bound: u32,
subtract_one: bool,
) -> Option<Vec<u32>> {
let mut live_entry_ids = Vec::new();
for entry_id in 1..=live_id_bound {
let bit_index = if subtract_one {
entry_id.checked_sub(1)?
} else {
entry_id
};
let byte_index = usize::try_from(bit_index / 8).ok()?;
let bit_mask = 1u8.checked_shl(bit_index % 8).unwrap_or(0);
let tombstone_byte = *bitset.get(byte_index)?;
if tombstone_byte & bit_mask == 0 {
live_entry_ids.push(entry_id);
}
}
Some(live_entry_ids)
}
fn parse_event_runtime_record_summaries(
records_payload: &[u8],
records_payload_offset: usize,
live_entry_ids: &[u32],
) -> Vec<SmpLoadedPackedEventRecordSummary> {
try_parse_synthetic_event_runtime_record_summaries(
records_payload,
records_payload_offset,
live_entry_ids,
)
.or_else(|| {
try_parse_real_event_runtime_record_summaries(
records_payload,
records_payload_offset,
live_entry_ids,
)
})
.unwrap_or_else(|| {
build_unsupported_event_runtime_record_summaries(
live_entry_ids,
"0x4e9a payload did not match the current packed-event record decode harness",
)
})
}
fn try_parse_synthetic_event_runtime_record_summaries(
records_payload: &[u8],
records_payload_offset: usize,
live_entry_ids: &[u32],
) -> Option<Vec<SmpLoadedPackedEventRecordSummary>> {
if !records_payload.starts_with(PACKED_EVENT_RECORDS_SYNTHETIC_MAGIC) {
return None;
}
let mut cursor = PACKED_EVENT_RECORDS_SYNTHETIC_MAGIC.len();
let mut records = Vec::with_capacity(live_entry_ids.len());
for (record_index, live_entry_id) in live_entry_ids.iter().copied().enumerate() {
let record_len = usize::try_from(read_u32_at(records_payload, cursor)?).ok()?;
cursor += 4;
let record_body = records_payload.get(cursor..cursor + record_len)?;
records.push(parse_synthetic_event_runtime_record_summary(
record_body,
records_payload_offset + cursor,
record_index,
live_entry_id,
)?);
cursor += record_len;
}
if cursor != records_payload.len() {
return None;
}
Some(records)
}
fn parse_synthetic_event_runtime_record_summary(
record_body: &[u8],
payload_offset: usize,
record_index: usize,
live_entry_id: u32,
) -> Option<SmpLoadedPackedEventRecordSummary> {
if !record_body.starts_with(PACKED_EVENT_RECORD_SYNTHETIC_MAGIC) {
return None;
}
let mut cursor = PACKED_EVENT_RECORD_SYNTHETIC_MAGIC.len();
let trigger_kind = read_u8_at(record_body, cursor)?;
cursor += 1;
let flags = read_u8_at(record_body, cursor)?;
cursor += 1;
let standalone_condition_row_count = usize::from(read_u8_at(record_body, cursor)?);
cursor += 1;
let action_count = usize::from(read_u8_at(record_body, cursor)?);
cursor += 1;
let mut grouped_effect_row_counts = Vec::with_capacity(4);
for _ in 0..4 {
grouped_effect_row_counts.push(usize::from(read_u8_at(record_body, cursor)?));
cursor += 1;
}
let mut text_bands = Vec::with_capacity(PACKED_EVENT_TEXT_BAND_LABELS.len());
for label in PACKED_EVENT_TEXT_BAND_LABELS {
let packed_len = usize::from(read_u16_at(record_body, cursor)?);
cursor += 2;
let band_bytes = record_body.get(cursor..cursor + packed_len)?;
cursor += packed_len;
text_bands.push(SmpLoadedPackedEventTextBandSummary {
label: label.to_string(),
packed_len,
present: packed_len != 0,
preview: ascii_preview(band_bytes),
});
}
let mut decoded_actions = Vec::with_capacity(action_count);
for _ in 0..action_count {
decoded_actions.push(parse_synthetic_packed_event_action(
record_body,
&mut cursor,
)?);
}
if cursor != record_body.len() {
return None;
}
let executable_import_ready = decoded_actions
.iter()
.all(runtime_effect_supported_for_save_import);
Some(SmpLoadedPackedEventRecordSummary {
record_index,
live_entry_id,
payload_offset: Some(payload_offset),
payload_len: Some(record_body.len()),
decode_status: if executable_import_ready {
"executable".to_string()
} else {
"parity_only".to_string()
},
payload_family: "synthetic_harness".to_string(),
trigger_kind: Some(trigger_kind),
active: Some(flags & 0x01 != 0),
marks_collection_dirty: Some(flags & 0x02 != 0),
one_shot: Some(flags & 0x04 != 0),
compact_control: None,
text_bands,
standalone_condition_row_count,
standalone_condition_rows: Vec::new(),
negative_sentinel_scope: None,
grouped_effect_row_counts,
grouped_effect_rows: Vec::new(),
decoded_conditions: Vec::new(),
decoded_actions,
executable_import_ready,
notes: vec!["decoded from the current synthetic packed-event record harness".to_string()],
})
}
fn try_parse_real_event_runtime_record_summaries(
records_payload: &[u8],
records_payload_offset: usize,
live_entry_ids: &[u32],
) -> Option<Vec<SmpLoadedPackedEventRecordSummary>> {
let mut cursor = 0usize;
let mut records = Vec::with_capacity(live_entry_ids.len());
for (record_index, live_entry_id) in live_entry_ids.iter().copied().enumerate() {
let (record, consumed_len) = parse_real_event_runtime_record_summary(
records_payload.get(cursor..)?,
records_payload_offset + cursor,
record_index,
live_entry_id,
)?;
records.push(record);
cursor += consumed_len;
}
if cursor != records_payload.len() {
return None;
}
Some(records)
}
fn parse_real_event_runtime_record_summary(
record_body: &[u8],
payload_offset: usize,
record_index: usize,
live_entry_id: u32,
) -> Option<(SmpLoadedPackedEventRecordSummary, usize)> {
let mut cursor = 0usize;
let mut text_bands = Vec::with_capacity(PACKED_EVENT_TEXT_BAND_LABELS.len());
for label in PACKED_EVENT_TEXT_BAND_LABELS {
let packed_len = usize::from(read_u16_at(record_body, cursor)?);
cursor += 2;
let band_bytes = record_body.get(cursor..cursor + packed_len)?;
cursor += packed_len;
text_bands.push(SmpLoadedPackedEventTextBandSummary {
label: label.to_string(),
packed_len,
present: packed_len != 0,
preview: ascii_preview(band_bytes),
});
}
let compact_control = parse_optional_real_compact_control_summary(record_body, &mut cursor)?;
if read_u16_at(record_body, cursor)? != PACKED_EVENT_REAL_CONDITION_MARKER {
return None;
}
cursor += 2;
let standalone_condition_row_count = usize::from(read_u16_at(record_body, cursor)?);
cursor += 2;
let mut standalone_condition_rows = Vec::with_capacity(standalone_condition_row_count);
for row_index in 0..standalone_condition_row_count {
let row_bytes = record_body.get(cursor..cursor + PACKED_EVENT_REAL_CONDITION_ROW_LEN)?;
cursor += PACKED_EVENT_REAL_CONDITION_ROW_LEN;
let candidate_name = parse_optional_u16_len_prefixed_string(record_body, &mut cursor)?;
standalone_condition_rows.push(parse_real_condition_row_summary(
row_bytes,
row_index,
candidate_name,
)?);
}
if read_u16_at(record_body, cursor)? != PACKED_EVENT_REAL_GROUPED_EFFECT_MARKER {
return None;
}
cursor += 2;
let mut grouped_effect_row_counts = Vec::with_capacity(4);
for _ in 0..4 {
grouped_effect_row_counts.push(usize::from(read_u16_at(record_body, cursor)?));
cursor += 2;
}
let mut grouped_effect_rows =
Vec::with_capacity(grouped_effect_row_counts.iter().sum::<usize>());
for (group_index, row_count) in grouped_effect_row_counts.iter().copied().enumerate() {
for row_index in 0..row_count {
let row_bytes =
record_body.get(cursor..cursor + PACKED_EVENT_REAL_GROUPED_EFFECT_ROW_LEN)?;
cursor += PACKED_EVENT_REAL_GROUPED_EFFECT_ROW_LEN;
let locomotive_name = parse_optional_u16_len_prefixed_string(record_body, &mut cursor)?;
grouped_effect_rows.push(parse_real_grouped_effect_row_summary(
row_bytes,
group_index,
row_index,
locomotive_name,
)?);
}
}
if let Some(control) = compact_control.as_ref() {
for row in &mut grouped_effect_rows {
let target_subject = derive_real_grouped_target_subject(row, control);
let target_scope_ordinal = control
.grouped_target_scope_ordinals_0x7fb
.get(row.group_index)
.copied();
row.grouped_target_subject = target_subject
.map(real_grouped_target_subject_name)
.map(str::to_string);
row.grouped_target_scope = derive_real_grouped_target_scope_name(
row,
control,
target_subject,
target_scope_ordinal,
);
let company_target_present = control
.grouped_target_scope_ordinals_0x7fb
.get(row.group_index)
.copied()
.and_then(real_grouped_company_target)
.is_some();
let chairman_target_present = control
.grouped_target_scope_ordinals_0x7fb
.get(row.group_index)
.copied()
.is_some_and(real_grouped_chairman_target_supported_in_runtime);
let territory_target_present = control
.grouped_territory_selectors_0x80f
.get(row.group_index)
.is_some_and(|selector| *selector >= 0);
if row.descriptor_id == 15
&& row.row_shape == "bool_toggle"
&& row.raw_scalar_value != 0
&& !company_target_present
&& !territory_target_present
{
row.notes
.push("retire train row is missing company and territory scope".to_string());
}
if row.descriptor_id == 3
&& row.row_shape == "bool_toggle"
&& row.raw_scalar_value != 0
&& (!company_target_present || !territory_target_present)
{
row.notes
.push("territory access row is missing company or territory scope".to_string());
}
if matches!(target_subject, Some(RealGroupedTargetSubject::Chairman))
&& !chairman_target_present
{
let ordinal = target_scope_ordinal.unwrap_or(u8::MAX);
row.notes.push(format!(
"chairman row uses unsupported grouped target scope ordinal {ordinal}"
));
}
}
}
let negative_sentinel_scope = compact_control.as_ref().and_then(|control| {
derive_negative_sentinel_scope_summary(&standalone_condition_rows, control)
});
let decoded_conditions =
decode_real_condition_rows(&standalone_condition_rows, negative_sentinel_scope.as_ref());
let decoded_actions = compact_control
.as_ref()
.map(|control| decode_real_grouped_effect_actions(&grouped_effect_rows, control))
.unwrap_or_default();
let ordinary_condition_row_count = standalone_condition_rows
.iter()
.filter(|row| row.raw_condition_id >= 0)
.count();
let executable_import_ready = !grouped_effect_rows.is_empty()
&& decoded_actions.len() == grouped_effect_rows.len()
&& decoded_conditions.len() == ordinary_condition_row_count
&& decoded_actions
.iter()
.all(runtime_effect_supported_for_save_import)
&& decoded_conditions
.iter()
.all(runtime_condition_supported_for_save_import);
let consumed_len = cursor;
Some((
SmpLoadedPackedEventRecordSummary {
record_index,
live_entry_id,
payload_offset: Some(payload_offset),
payload_len: Some(consumed_len),
decode_status: "parity_only".to_string(),
payload_family: "real_packed_v1".to_string(),
trigger_kind: compact_control.as_ref().map(|control| control.mode_byte_0x7ef),
active: None,
marks_collection_dirty: None,
one_shot: compact_control
.as_ref()
.map(|control| control.one_shot_header_0x7f5 != 0),
compact_control,
text_bands,
standalone_condition_row_count,
standalone_condition_rows,
negative_sentinel_scope,
grouped_effect_row_counts,
grouped_effect_rows,
decoded_conditions,
decoded_actions,
executable_import_ready,
notes: vec![
"decoded from grounded real 0x4e9a row framing".to_string(),
"grouped descriptor labels and target masks come from the checked-in effect table recovered around 0x006103a0".to_string(),
],
},
consumed_len,
))
}
fn parse_optional_real_compact_control_summary(
record_body: &[u8],
cursor: &mut usize,
) -> Option<Option<SmpLoadedPackedEventCompactControlSummary>> {
if read_u16_at(record_body, *cursor)? == PACKED_EVENT_REAL_CONDITION_MARKER {
return Some(None);
}
let end = cursor.checked_add(PACKED_EVENT_REAL_COMPACT_CONTROL_LEN)?;
let bytes = record_body.get(*cursor..end)?;
let mut local = 0usize;
let mode_byte_0x7ef = read_u8_at(bytes, local)?;
local += 1;
let primary_selector_0x7f0 = read_u32_at(bytes, local)?;
local += 4;
let grouped_mode_0x7f4 = read_u8_at(bytes, local)?;
local += 1;
let one_shot_header_0x7f5 = read_u32_at(bytes, local)?;
local += 4;
let modifier_flag_0x7f9 = read_u8_at(bytes, local)?;
local += 1;
let modifier_flag_0x7fa = read_u8_at(bytes, local)?;
local += 1;
let mut grouped_target_scope_ordinals_0x7fb = Vec::with_capacity(PACKED_EVENT_REAL_GROUP_COUNT);
for _ in 0..PACKED_EVENT_REAL_GROUP_COUNT {
grouped_target_scope_ordinals_0x7fb.push(read_u8_at(bytes, local)?);
local += 1;
}
let mut grouped_scope_checkboxes_0x7ff = Vec::with_capacity(PACKED_EVENT_REAL_GROUP_COUNT);
for _ in 0..PACKED_EVENT_REAL_GROUP_COUNT {
grouped_scope_checkboxes_0x7ff.push(read_u8_at(bytes, local)?);
local += 1;
}
let summary_toggle_0x800 = read_u8_at(bytes, local)?;
local += 1;
let mut grouped_territory_selectors_0x80f = Vec::with_capacity(PACKED_EVENT_REAL_GROUP_COUNT);
for _ in 0..PACKED_EVENT_REAL_GROUP_COUNT {
grouped_territory_selectors_0x80f.push(read_i32_at(bytes, local)?);
local += 4;
}
if local != bytes.len() {
return None;
}
if read_u16_at(record_body, end)? != PACKED_EVENT_REAL_CONDITION_MARKER {
return None;
}
*cursor = end;
Some(Some(SmpLoadedPackedEventCompactControlSummary {
mode_byte_0x7ef,
primary_selector_0x7f0,
grouped_mode_0x7f4,
one_shot_header_0x7f5,
modifier_flag_0x7f9,
modifier_flag_0x7fa,
grouped_target_scope_ordinals_0x7fb,
grouped_scope_checkboxes_0x7ff,
summary_toggle_0x800,
grouped_territory_selectors_0x80f,
}))
}
fn parse_real_condition_row_summary(
row_bytes: &[u8],
row_index: usize,
candidate_name: Option<String>,
) -> Option<SmpLoadedPackedEventConditionRowSummary> {
let raw_condition_id = read_u32_at(row_bytes, 0)? as i32;
let subtype = read_u8_at(row_bytes, 4)?;
let flag_bytes = row_bytes
.get(5..PACKED_EVENT_REAL_CONDITION_ROW_LEN)?
.to_vec();
let candidate_name_display = candidate_name.clone();
let candidate_name_ref = candidate_name_display.as_deref();
let ordinary_metadata = real_ordinary_condition_metadata(raw_condition_id);
let comparator = ordinary_metadata
.and_then(|_| decode_real_condition_comparator(subtype))
.map(condition_comparator_label);
let metric = ordinary_metadata
.map(|metadata| real_ordinary_condition_metric_label(metadata, candidate_name_ref));
let threshold = ordinary_metadata.and_then(|_| decode_real_condition_threshold(&flag_bytes));
let requires_candidate_name_binding = ordinary_metadata.is_some_and(|metadata| {
matches!(
metadata.kind,
RealOrdinaryConditionKind::Numeric(
RealOrdinaryConditionMetric::Territory(_)
| RealOrdinaryConditionMetric::CompanyTerritory(_)
)
) && candidate_name.is_some()
});
let mut notes = Vec::new();
if raw_condition_id < 0 {
notes.push("negative sentinel-style condition row id".to_string());
}
if candidate_name.is_some() {
notes.push("condition row carries candidate-name side string".to_string());
}
if ordinary_metadata.is_none() && raw_condition_id >= 0 {
notes.push(
"ordinary condition id is not yet recovered in the checked-in condition table"
.to_string(),
);
}
if ordinary_metadata.is_some_and(|metadata| {
matches!(
metadata.kind,
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CandidateAvailability)
) && candidate_name.is_none()
}) {
notes.push(
"candidate-availability condition row is missing its candidate-name side string"
.to_string(),
);
}
if ordinary_metadata.is_some_and(|metadata| {
matches!(
metadata.kind,
RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::NamedLocomotiveAvailability
| RealWorldConditionKind::NamedLocomotiveCost
)
) && candidate_name.is_none()
}) {
notes.push("named locomotive condition row is missing its side-string binding".to_string());
}
if ordinary_metadata.is_some_and(|metadata| {
matches!(
metadata.kind,
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CargoProductionSlot)
) && candidate_name.is_none()
}) {
notes.push(
"named cargo-production condition row is missing its side-string binding".to_string(),
);
}
if ordinary_metadata.is_some_and(|metadata| {
matches!(
metadata.kind,
RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::FactoryProductionTotal
| RealWorldConditionKind::FarmMineProductionTotal
| RealWorldConditionKind::OtherCargoProductionTotal
)
)
}) {
notes.push(
"checked-in RT3.lng label is known, but this cargo aggregate condition family is not yet lowered"
.to_string(),
);
}
if ordinary_metadata.is_some_and(|metadata| {
matches!(
metadata.kind,
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CargoProductionSlot)
) && candidate_name_ref
.and_then(recovered_cargo_production_slot_from_condition_name)
.is_none()
}) {
notes.push(
"named cargo-production condition side string does not yet map to a checked-in cargo slot"
.to_string(),
);
}
Some(SmpLoadedPackedEventConditionRowSummary {
row_index,
raw_condition_id,
subtype,
flag_bytes,
candidate_name,
comparator,
metric,
semantic_family: ordinary_metadata
.map(|metadata| real_ordinary_condition_semantic_family(metadata).to_string()),
semantic_preview: ordinary_metadata.and_then(|metadata| {
threshold.map(|value| {
let comparator_text = decode_real_condition_comparator(subtype)
.map(condition_comparator_symbol)
.unwrap_or("?");
let metric_label =
real_ordinary_condition_metric_label(metadata, candidate_name_ref);
format!("Test {} {} {}", metric_label, comparator_text, value)
})
}),
recovered_cargo_slot: ordinary_metadata.and_then(|metadata| match metadata.kind {
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CargoProductionSlot) => {
candidate_name_ref.and_then(recovered_cargo_production_slot_from_condition_name)
}
_ => None,
}),
recovered_cargo_class: ordinary_metadata.and_then(|metadata| match metadata.kind {
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CargoProductionSlot) => {
candidate_name_ref
.and_then(recovered_cargo_production_slot_from_condition_name)
.and_then(known_cargo_slot_definition)
.map(|definition| runtime_cargo_class_name(definition.cargo_class).to_string())
}
RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::FactoryProductionTotal,
) => Some("factory".to_string()),
RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::FarmMineProductionTotal,
) => Some("farm_mine".to_string()),
RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::OtherCargoProductionTotal,
) => Some("other".to_string()),
_ => None,
}),
requires_candidate_name_binding,
notes,
})
}
fn derive_negative_sentinel_scope_summary(
rows: &[SmpLoadedPackedEventConditionRowSummary],
control: &SmpLoadedPackedEventCompactControlSummary,
) -> Option<SmpLoadedPackedEventNegativeSentinelScopeSummary> {
let source_row_indexes = rows
.iter()
.filter(|row| row.raw_condition_id == -1)
.map(|row| row.row_index)
.collect::<Vec<_>>();
if source_row_indexes.is_empty() {
return None;
}
Some(SmpLoadedPackedEventNegativeSentinelScopeSummary {
company_test_scope: decode_company_condition_test_scope(control.modifier_flag_0x7f9)?,
player_test_scope: decode_player_condition_test_scope(control.modifier_flag_0x7fa)?,
territory_scope_selector_is_0x63: control.primary_selector_0x7f0 == 0x63,
source_row_indexes,
})
}
fn decode_company_condition_test_scope(value: u8) -> Option<RuntimeCompanyConditionTestScope> {
match value {
0 => Some(RuntimeCompanyConditionTestScope::Disabled),
1 => Some(RuntimeCompanyConditionTestScope::AllCompanies),
2 => Some(RuntimeCompanyConditionTestScope::SelectedCompanyOnly),
3 => Some(RuntimeCompanyConditionTestScope::AiCompaniesOnly),
4 => Some(RuntimeCompanyConditionTestScope::HumanCompaniesOnly),
_ => None,
}
}
fn decode_player_condition_test_scope(value: u8) -> Option<RuntimePlayerConditionTestScope> {
match value {
0 => Some(RuntimePlayerConditionTestScope::Disabled),
1 => Some(RuntimePlayerConditionTestScope::AllPlayers),
2 => Some(RuntimePlayerConditionTestScope::SelectedPlayerOnly),
3 => Some(RuntimePlayerConditionTestScope::AiPlayersOnly),
4 => Some(RuntimePlayerConditionTestScope::HumanPlayersOnly),
_ => None,
}
}
fn real_ordinary_condition_metadata(
raw_condition_id: i32,
) -> Option<RealOrdinaryConditionMetadata> {
REAL_ORDINARY_CONDITION_METADATA
.iter()
.copied()
.find(|metadata| metadata.raw_condition_id == raw_condition_id)
.or_else(|| {
known_special_condition_definition_for_label_id(raw_condition_id as u32).map(
|definition| {
let kind = if let Some(world_toggle) =
real_grouped_effect_descriptor_metadata(110 + definition.slot_index as u32)
.filter(|metadata| metadata.parameter_family == "world_flag_toggle")
{
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::WorldFlag {
key: world_toggle.runtime_key.unwrap_or(world_toggle.label),
})
} else {
RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::SpecialCondition {
label: definition.label,
},
)
};
RealOrdinaryConditionMetadata {
raw_condition_id,
label: definition.label,
kind,
}
},
)
})
}
fn real_ordinary_condition_metric_label(
metadata: RealOrdinaryConditionMetadata,
candidate_name: Option<&str>,
) -> String {
match metadata.kind {
RealOrdinaryConditionKind::Numeric(_) => metadata.label.to_string(),
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::SpecialCondition {
label,
}) => {
format!("Special Condition: {label}")
}
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CandidateAvailability) => {
match candidate_name {
Some(name) => format!("Candidate Availability: {name}"),
None => "Candidate Availability".to_string(),
}
}
RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::NamedLocomotiveAvailability,
) => match candidate_name {
Some(name) => format!("Named Locomotive Availability: {name}"),
None => "Named Locomotive Availability".to_string(),
},
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::NamedLocomotiveCost) => {
match candidate_name {
Some(name) => format!("Named Locomotive Cost: {name}"),
None => "Named Locomotive Cost".to_string(),
}
}
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CargoProductionSlot) => {
match candidate_name {
Some(name) => format!("Cargo Production: {name}"),
None => "Cargo Production".to_string(),
}
}
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CargoProductionTotal) => {
"Cargo Production Total".to_string()
}
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::FactoryProductionTotal) => {
"Factory Production Total".to_string()
}
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::FarmMineProductionTotal) => {
"Farm/Mine Production Total".to_string()
}
RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::OtherCargoProductionTotal,
) => "Other Cargo Production Total".to_string(),
RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::LimitedTrackBuildingAmount,
) => "Limited Track Building Amount".to_string(),
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::TerritoryAccessCost) => {
"Territory Access Cost".to_string()
}
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::EconomicStatus) => {
"Economic Status".to_string()
}
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::WorldFlag { .. }) => {
format!("World Flag: {}", metadata.label)
}
}
}
fn real_ordinary_condition_semantic_family(
metadata: RealOrdinaryConditionMetadata,
) -> &'static str {
match metadata.kind {
RealOrdinaryConditionKind::Numeric(_) => "numeric_threshold",
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::WorldFlag { .. }) => {
"world_flag_equals"
}
RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::NamedLocomotiveAvailability
| RealWorldConditionKind::NamedLocomotiveCost
| RealWorldConditionKind::CargoProductionSlot
| RealWorldConditionKind::CargoProductionTotal
| RealWorldConditionKind::FactoryProductionTotal
| RealWorldConditionKind::FarmMineProductionTotal
| RealWorldConditionKind::OtherCargoProductionTotal
| RealWorldConditionKind::LimitedTrackBuildingAmount
| RealWorldConditionKind::TerritoryAccessCost,
) => "world_scalar_threshold",
RealOrdinaryConditionKind::WorldState(_) => "world_state_threshold",
}
}
fn decode_real_condition_comparator(subtype: u8) -> Option<RuntimeConditionComparator> {
match subtype {
0 => Some(RuntimeConditionComparator::Ge),
1 => Some(RuntimeConditionComparator::Le),
2 => Some(RuntimeConditionComparator::Gt),
3 => Some(RuntimeConditionComparator::Lt),
4 => Some(RuntimeConditionComparator::Eq),
5 => Some(RuntimeConditionComparator::Ne),
_ => None,
}
}
fn decode_real_condition_threshold(flag_bytes: &[u8]) -> Option<i64> {
let raw = flag_bytes.get(0..4)?;
let mut bytes = [0u8; 4];
bytes.copy_from_slice(raw);
Some(i32::from_le_bytes(bytes).into())
}
fn condition_comparator_label(comparator: RuntimeConditionComparator) -> String {
match comparator {
RuntimeConditionComparator::Ge => "ge".to_string(),
RuntimeConditionComparator::Le => "le".to_string(),
RuntimeConditionComparator::Gt => "gt".to_string(),
RuntimeConditionComparator::Lt => "lt".to_string(),
RuntimeConditionComparator::Eq => "eq".to_string(),
RuntimeConditionComparator::Ne => "ne".to_string(),
}
}
fn condition_comparator_symbol(comparator: RuntimeConditionComparator) -> &'static str {
match comparator {
RuntimeConditionComparator::Ge => ">=",
RuntimeConditionComparator::Le => "<=",
RuntimeConditionComparator::Gt => ">",
RuntimeConditionComparator::Lt => "<",
RuntimeConditionComparator::Eq => "==",
RuntimeConditionComparator::Ne => "!=",
}
}
fn parse_real_grouped_effect_row_summary(
row_bytes: &[u8],
group_index: usize,
row_index: usize,
locomotive_name: Option<String>,
) -> Option<SmpLoadedPackedEventGroupedEffectRowSummary> {
let descriptor_id = read_u32_at(row_bytes, 0)?;
let raw_scalar_value = read_u32_at(row_bytes, 4)? as i32;
let opcode = read_u8_at(row_bytes, 8)?;
let value_byte_0x09 = read_u8_at(row_bytes, 9)?;
let value_dword_0x0d = read_u32_at(row_bytes, 0x0d)?;
let value_byte_0x11 = read_u8_at(row_bytes, 0x11)?;
let value_byte_0x12 = read_u8_at(row_bytes, 0x12)?;
let value_word_0x14 = read_u16_at(row_bytes, 0x14)?;
let value_word_0x16 = read_u16_at(row_bytes, 0x16)?;
let descriptor_metadata = real_grouped_effect_descriptor_metadata(descriptor_id);
let mut row_shape = classify_real_grouped_effect_row_shape(
opcode,
raw_scalar_value,
value_byte_0x11,
value_byte_0x12,
value_word_0x14,
value_word_0x16,
)
.to_string();
let mut semantic_family = classify_real_grouped_effect_semantic_family(
opcode,
raw_scalar_value,
value_byte_0x11,
value_byte_0x12,
value_word_0x14,
value_word_0x16,
)
.to_string();
if descriptor_metadata.is_some_and(|metadata| {
matches!(
metadata.parameter_family,
"special_condition_scalar" | "candidate_availability_scalar"
) && opcode == 3
&& value_byte_0x11 == 0
&& value_byte_0x12 == 0
&& value_word_0x14 == 0
&& value_word_0x16 == 0
}) {
row_shape = "scalar_assignment".to_string();
semantic_family = "scalar_assignment".to_string();
}
let mut notes = Vec::new();
if locomotive_name.is_some() {
notes.push("grouped effect row carries locomotive-name side string".to_string());
}
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) {
notes.push(format!(
"locomotive availability descriptor maps to live locomotive id {loco_id}"
));
}
if let Some(loco_id) = recovered_locomotive_cost_loco_id(descriptor_id) {
notes.push(format!(
"locomotive cost descriptor maps to live locomotive id {loco_id}"
));
}
if let Some(cargo_slot) = recovered_cargo_production_slot(descriptor_id) {
notes.push(format!(
"cargo-production descriptor maps to world production slot {cargo_slot}"
));
}
if let Some(cargo_label) = grounded_named_cargo_production_label(descriptor_id) {
notes.push(format!(
"named cargo production descriptor maps to cargo {cargo_label}"
));
}
if descriptor_metadata.is_some_and(|metadata| metadata.parameter_family == "cargo_price_scalar")
{
if let Some(cargo_label) = grounded_named_cargo_price_label(descriptor_id) {
notes.push(format!(
"named cargo price descriptor maps to cargo {cargo_label}"
));
}
}
Some(SmpLoadedPackedEventGroupedEffectRowSummary {
group_index,
row_index,
descriptor_id,
descriptor_label: descriptor_metadata.map(|metadata| metadata.label.to_string()),
target_mask_bits: descriptor_metadata.map(|metadata| metadata.target_mask_bits),
parameter_family: descriptor_metadata.map(|metadata| metadata.parameter_family.to_string()),
grouped_target_subject: None,
grouped_target_scope: None,
opcode,
raw_scalar_value,
value_byte_0x09,
value_dword_0x0d,
value_byte_0x11,
value_byte_0x12,
value_word_0x14,
value_word_0x16,
row_shape,
semantic_family: Some(semantic_family.clone()),
semantic_preview: Some(build_real_grouped_effect_semantic_preview(
descriptor_metadata.map(|metadata| metadata.label),
&semantic_family,
raw_scalar_value,
value_byte_0x11,
value_byte_0x12,
value_word_0x14,
value_word_0x16,
)),
recovered_cargo_slot: recovered_cargo_production_slot(descriptor_id),
recovered_cargo_class: recovered_cargo_production_slot(descriptor_id)
.and_then(known_cargo_slot_definition)
.map(|definition| runtime_cargo_class_name(definition.cargo_class).to_string()),
recovered_cargo_label: grounded_named_cargo_production_label(descriptor_id)
.or_else(|| {
descriptor_metadata
.filter(|metadata| metadata.parameter_family == "cargo_price_scalar")
.and_then(|_| grounded_named_cargo_price_label(descriptor_id))
})
.map(ToString::to_string),
recovered_locomotive_id: recovered_locomotive_availability_loco_id(descriptor_id)
.or_else(|| recovered_locomotive_cost_loco_id(descriptor_id)),
locomotive_name,
notes,
})
}
fn decode_real_condition_rows(
rows: &[SmpLoadedPackedEventConditionRowSummary],
negative_sentinel_scope: Option<&SmpLoadedPackedEventNegativeSentinelScopeSummary>,
) -> Vec<RuntimeCondition> {
rows.iter()
.filter(|row| row.raw_condition_id >= 0)
.filter_map(|row| decode_real_condition_row(row, negative_sentinel_scope))
.collect()
}
fn decode_real_condition_row(
row: &SmpLoadedPackedEventConditionRowSummary,
negative_sentinel_scope: Option<&SmpLoadedPackedEventNegativeSentinelScopeSummary>,
) -> Option<RuntimeCondition> {
let metadata = real_ordinary_condition_metadata(row.raw_condition_id)?;
let comparator = decode_real_condition_comparator(row.subtype)?;
let value = decode_real_condition_threshold(&row.flag_bytes)?;
match metadata.kind {
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(index)) => {
Some(RuntimeCondition::WorldVariableThreshold {
index,
comparator,
value,
})
}
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(metric)) => {
Some(RuntimeCondition::CompanyNumericThreshold {
target: RuntimeCompanyTarget::ConditionTrueCompany,
metric,
comparator,
value,
})
}
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(index)) => {
Some(RuntimeCondition::CompanyVariableThreshold {
target: RuntimeCompanyTarget::ConditionTrueCompany,
index,
comparator,
value,
})
}
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(index)) => {
negative_sentinel_scope.and_then(|scope| {
real_condition_player_target(scope).map(|target| {
RuntimeCondition::PlayerVariableThreshold {
target,
index,
comparator,
value,
}
})
})
}
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(metric)) => {
negative_sentinel_scope.and_then(|scope| {
real_condition_chairman_target(scope).map(|target| {
RuntimeCondition::ChairmanNumericThreshold {
target,
metric,
comparator,
value,
}
})
})
}
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(
index,
)) => negative_sentinel_scope
.filter(|scope| scope.territory_scope_selector_is_0x63)
.map(|_| RuntimeCondition::TerritoryVariableThreshold {
target: RuntimeTerritoryTarget::AllTerritories,
index,
comparator,
value,
}),
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(metric)) => {
negative_sentinel_scope
.filter(|scope| scope.territory_scope_selector_is_0x63)
.map(|_| RuntimeCondition::TerritoryNumericThreshold {
target: RuntimeTerritoryTarget::AllTerritories,
metric,
comparator,
value,
})
}
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
metric,
)) => negative_sentinel_scope
.filter(|scope| scope.territory_scope_selector_is_0x63)
.map(|_| RuntimeCondition::CompanyTerritoryNumericThreshold {
target: RuntimeCompanyTarget::ConditionTrueCompany,
territory: RuntimeTerritoryTarget::AllTerritories,
metric,
comparator,
value,
}),
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::SpecialCondition {
label,
}) => Some(RuntimeCondition::SpecialConditionThreshold {
label: label.to_string(),
comparator,
value,
}),
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CandidateAvailability) => row
.candidate_name
.as_ref()
.map(|name| RuntimeCondition::CandidateAvailabilityThreshold {
name: name.clone(),
comparator,
value,
}),
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CargoProductionSlot) => {
row.candidate_name.as_ref().and_then(|name| {
recovered_cargo_production_slot_from_condition_name(name).map(|slot| {
let label = known_cargo_slot_definition(slot)
.map(|definition| definition.label.to_string())
.unwrap_or_else(|| name.clone());
RuntimeCondition::CargoProductionSlotThreshold {
slot,
label,
comparator,
value,
}
})
})
}
RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::NamedLocomotiveAvailability,
) => row.candidate_name.as_ref().map(|name| {
RuntimeCondition::NamedLocomotiveAvailabilityThreshold {
name: name.clone(),
comparator,
value,
}
}),
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::NamedLocomotiveCost) => row
.candidate_name
.as_ref()
.map(|name| RuntimeCondition::NamedLocomotiveCostThreshold {
name: name.clone(),
comparator,
value,
}),
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CargoProductionTotal) => {
Some(RuntimeCondition::CargoProductionTotalThreshold { comparator, value })
}
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::FactoryProductionTotal) => {
Some(RuntimeCondition::FactoryProductionTotalThreshold { comparator, value })
}
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::FarmMineProductionTotal) => {
Some(RuntimeCondition::FarmMineProductionTotalThreshold { comparator, value })
}
RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::OtherCargoProductionTotal,
) => Some(RuntimeCondition::OtherCargoProductionTotalThreshold { comparator, value }),
RealOrdinaryConditionKind::WorldState(
RealWorldConditionKind::LimitedTrackBuildingAmount,
) => Some(RuntimeCondition::LimitedTrackBuildingAmountThreshold { comparator, value }),
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::TerritoryAccessCost) => {
Some(RuntimeCondition::TerritoryAccessCostThreshold { comparator, value })
}
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::EconomicStatus) => {
Some(RuntimeCondition::EconomicStatusCodeThreshold { comparator, value })
}
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::WorldFlag { key }) => {
decode_world_flag_condition(comparator, value, key)
}
}
}
fn decode_world_flag_condition(
comparator: RuntimeConditionComparator,
value: i64,
key: &'static str,
) -> Option<RuntimeCondition> {
let bool_value = match (comparator, value) {
(RuntimeConditionComparator::Eq, 0) | (RuntimeConditionComparator::Ne, 1) => false,
(RuntimeConditionComparator::Eq, 1) | (RuntimeConditionComparator::Ne, 0) => true,
_ => return None,
};
Some(RuntimeCondition::WorldFlagEquals {
key: key.to_string(),
value: bool_value,
})
}
fn real_condition_chairman_target(
scope: &SmpLoadedPackedEventNegativeSentinelScopeSummary,
) -> Option<RuntimeChairmanTarget> {
match scope.player_test_scope {
RuntimePlayerConditionTestScope::AllPlayers => Some(RuntimeChairmanTarget::AllActive),
RuntimePlayerConditionTestScope::SelectedPlayerOnly => {
Some(RuntimeChairmanTarget::SelectedChairman)
}
RuntimePlayerConditionTestScope::AiPlayersOnly => Some(RuntimeChairmanTarget::AiChairmen),
RuntimePlayerConditionTestScope::HumanPlayersOnly => {
Some(RuntimeChairmanTarget::HumanChairmen)
}
RuntimePlayerConditionTestScope::Disabled => None,
}
}
fn real_condition_player_target(
scope: &SmpLoadedPackedEventNegativeSentinelScopeSummary,
) -> Option<RuntimePlayerTarget> {
match scope.player_test_scope {
RuntimePlayerConditionTestScope::AllPlayers => Some(RuntimePlayerTarget::AllActive),
RuntimePlayerConditionTestScope::SelectedPlayerOnly => {
Some(RuntimePlayerTarget::SelectedPlayer)
}
RuntimePlayerConditionTestScope::AiPlayersOnly => Some(RuntimePlayerTarget::AiPlayers),
RuntimePlayerConditionTestScope::HumanPlayersOnly => {
Some(RuntimePlayerTarget::HumanPlayers)
}
RuntimePlayerConditionTestScope::Disabled => None,
}
}
fn real_grouped_effect_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
recovered_cargo_price_descriptor_metadata(descriptor_id)
.or_else(|| recovered_cargo_economics_descriptor_metadata(descriptor_id))
.or_else(|| 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_price_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
(descriptor_id == 105).then_some(RealGroupedEffectDescriptorMetadata {
descriptor_id,
label: "All Cargo Prices",
target_mask_bits: 0x08,
parameter_family: "cargo_price_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
})
}
fn recovered_cargo_economics_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
match descriptor_id {
177 => Some(RealGroupedEffectDescriptorMetadata {
descriptor_id,
label: "All Cargo Production",
target_mask_bits: 0x08,
parameter_family: "cargo_production_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
}),
178 => Some(RealGroupedEffectDescriptorMetadata {
descriptor_id,
label: "All Factory Production",
target_mask_bits: 0x08,
parameter_family: "cargo_production_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
}),
179 => Some(RealGroupedEffectDescriptorMetadata {
descriptor_id,
label: "All Farm/Mine Production",
target_mask_bits: 0x08,
parameter_family: "cargo_production_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
}),
_ => None,
}
}
const GROUNDED_NAMED_CARGO_PRODUCTION_LABELS: [(&str, &str); 50] = [
("Alcohol", "Alcohol Production"),
("Aluminum", "Aluminum Production"),
("Ammunition", "Ammunition Production"),
("Automobiles", "Automobiles Production"),
("Bauxite", "Bauxite Production"),
("Ceramics", "Ceramics Production"),
("Cheese", "Cheese Production"),
("Chemicals", "Chemicals Production"),
("Clothing", "Clothing Production"),
("Coal", "Coal Production"),
("Coffee", "Coffee Production"),
("Concrete", "Concrete Production"),
("Corn", "Corn Production"),
("Cotton", "Cotton Production"),
("Crystals", "Crystals Production"),
("Diesel", "Diesel Production"),
("Dye", "Dye Production"),
("Electronics", "Electronics Production"),
("Fertilizer", "Fertilizer Production"),
("Furniture", "Furniture Production"),
("Goods", "Goods Production"),
("Grain", "Grain Production"),
("Ingots", "Ingots Production"),
("Iron", "Iron Production"),
("Livestock", "Livestock Production"),
("Logs", "Logs Production"),
("Lumber", "Lumber Production"),
("Machinery", "Machinery Production"),
("Mail", "Mail Production"),
("Meat", "Meat Production"),
("Medicine", "Medicine Production"),
("Milk", "Milk Production"),
("Oil", "Oil Production"),
("Ore", "Ore Production"),
("Paper", "Paper Production"),
("Passengers", "Passengers Production"),
("Plastic", "Plastic Production"),
("Produce", "Produce Production"),
("Pulpwood", "Pulpwood Production"),
("Rice", "Rice Production"),
("Rubber", "Rubber Production"),
("Steel", "Steel Production"),
("Sugar", "Sugar Production"),
("Tires", "Tires Production"),
("Toys", "Toys Production"),
("Troops", "Troops Production"),
("Uranium", "Uranium Production"),
("Waste", "Waste Production"),
("Weapons", "Weapons Production"),
("Wool", "Wool Production"),
];
#[derive(Debug, Deserialize)]
struct CheckedInCargoBindingsArtifact {
bindings: Vec<CheckedInCargoBindingRow>,
}
#[derive(Debug, Deserialize)]
struct CheckedInCargoBindingRow {
descriptor_id: u32,
band: String,
cargo_name: String,
}
fn grounded_named_cargo_price_bindings() -> &'static BTreeMap<u32, (&'static str, &'static str)> {
static BINDINGS: OnceLock<BTreeMap<u32, (&'static str, &'static str)>> = OnceLock::new();
BINDINGS.get_or_init(|| {
let artifact: CheckedInCargoBindingsArtifact = serde_json::from_str(include_str!(
"../../../artifacts/exports/rt3-1.06/event-effects-cargo-bindings.json"
))
.expect("checked-in cargo bindings artifact should parse");
artifact
.bindings
.into_iter()
.filter(|binding| binding.band == "cargo_price_named")
.map(|binding| {
let cargo_name = Box::leak(binding.cargo_name.into_boxed_str()) as &'static str;
let descriptor_label =
Box::leak(format!("{cargo_name} Price").into_boxed_str()) as &'static str;
(binding.descriptor_id, (cargo_name, descriptor_label))
})
.collect()
})
}
pub(crate) fn grounded_named_cargo_price_label(descriptor_id: u32) -> Option<&'static str> {
grounded_named_cargo_price_bindings()
.get(&descriptor_id)
.map(|(cargo_label, _)| *cargo_label)
}
fn grounded_named_cargo_production_label(descriptor_id: u32) -> Option<&'static str> {
let index = descriptor_id.checked_sub(180)? as usize;
GROUNDED_NAMED_CARGO_PRODUCTION_LABELS
.get(index)
.map(|(cargo_label, _)| *cargo_label)
}
fn grounded_named_cargo_production_descriptor_label(descriptor_id: u32) -> Option<&'static str> {
let index = descriptor_id.checked_sub(180)? as usize;
GROUNDED_NAMED_CARGO_PRODUCTION_LABELS
.get(index)
.map(|(_, descriptor_label)| *descriptor_label)
}
fn recovered_cargo_production_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
if let Some(label) = grounded_named_cargo_production_descriptor_label(descriptor_id) {
return Some(RealGroupedEffectDescriptorMetadata {
descriptor_id,
label,
target_mask_bits: 0x08,
parameter_family: "cargo_production_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
});
}
recovered_cargo_production_label(descriptor_id).map(|label| {
RealGroupedEffectDescriptorMetadata {
descriptor_id,
label,
target_mask_bits: 0x08,
parameter_family: "cargo_production_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
}
})
}
fn recovered_cargo_production_slot(descriptor_id: u32) -> Option<u32> {
let slot = descriptor_id.checked_sub(229)?;
(1..=11).contains(&slot).then_some(slot)
}
fn recovered_locomotive_availability_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
if let Some(loco_id) = recovered_locomotive_availability_loco_id(descriptor_id) {
let label = recovered_locomotive_availability_label(loco_id);
let executable_in_runtime = (loco_id as usize) <= GROUNDED_LOCOMOTIVE_PREFIX.len();
return Some(RealGroupedEffectDescriptorMetadata {
descriptor_id,
label,
target_mask_bits: 0x08,
parameter_family: "locomotive_availability_scalar",
runtime_key: None,
runtime_status: if executable_in_runtime {
RealGroupedEffectRuntimeStatus::Executable
} else {
RealGroupedEffectRuntimeStatus::EvidenceBlocked
},
executable_in_runtime,
});
}
(457..=474)
.contains(&descriptor_id)
.then(|| RealGroupedEffectDescriptorMetadata {
descriptor_id,
label: upper_band_locomotive_availability_label(descriptor_id),
target_mask_bits: 0x08,
parameter_family: "locomotive_availability_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::EvidenceBlocked,
executable_in_runtime: false,
})
}
fn recovered_locomotive_availability_loco_id(descriptor_id: u32) -> Option<u32> {
if (241..=351).contains(&descriptor_id) {
return Some(descriptor_id - 240);
}
None
}
fn grounded_locomotive_name(loco_id: u32) -> Option<&'static str> {
let index = loco_id.checked_sub(1)? as usize;
GROUNDED_LOCOMOTIVE_PREFIX.get(index).copied()
}
fn recovered_locomotive_availability_label(loco_id: u32) -> &'static str {
static LABELS: OnceLock<BTreeMap<u32, &'static str>> = OnceLock::new();
LABELS
.get_or_init(|| {
(1..=111)
.map(|loco_id| {
let label = grounded_locomotive_name(loco_id)
.map(|name| format!("{name} Availability"))
.unwrap_or_else(|| {
format!("Lower-Band Locomotive Availability Slot {loco_id}")
});
(loco_id, Box::leak(label.into_boxed_str()) as &'static str)
})
.collect()
})
.get(&loco_id)
.copied()
.expect("lower-band locomotive availability label should exist")
}
fn upper_band_locomotive_availability_label(descriptor_id: u32) -> &'static str {
static LABELS: OnceLock<BTreeMap<u32, &'static str>> = OnceLock::new();
LABELS
.get_or_init(|| {
(457..=474)
.map(|descriptor_id| {
let label = format!(
"Upper-Band Locomotive Availability Slot {}",
descriptor_id - 456
);
(
descriptor_id,
Box::leak(label.into_boxed_str()) as &'static str,
)
})
.collect()
})
.get(&descriptor_id)
.copied()
.expect("upper-band locomotive availability label should exist")
}
fn recovered_cargo_production_label(descriptor_id: u32) -> Option<&'static str> {
known_cargo_slot_definition_for_descriptor_id(descriptor_id).map(|definition| definition.label)
}
fn recovered_cargo_production_slot_from_condition_name(name: &str) -> Option<u32> {
KNOWN_CARGO_SLOT_DEFINITIONS
.iter()
.find(|definition| definition.label == name)
.map(|definition| definition.slot_id)
}
fn recovered_locomotive_cost_loco_id(descriptor_id: u32) -> Option<u32> {
if (352..=451).contains(&descriptor_id) {
return Some(descriptor_id - 351);
}
None
}
fn recovered_locomotive_cost_label(descriptor_id: u32) -> Option<&'static str> {
static LABELS: OnceLock<BTreeMap<u32, &'static str>> = OnceLock::new();
LABELS
.get_or_init(|| {
(352..=451)
.filter_map(|descriptor_id| {
recovered_locomotive_cost_loco_id(descriptor_id).map(|loco_id| {
let label = grounded_locomotive_name(loco_id)
.map(|name| format!("{name} Cost"))
.unwrap_or_else(|| {
format!("Lower-Band Locomotive Cost Slot {loco_id}")
});
let label = Box::leak(label.into_boxed_str()) as &'static str;
(descriptor_id, label)
})
})
.collect()
})
.get(&descriptor_id)
.copied()
.or_else(|| upper_band_locomotive_cost_label(descriptor_id))
}
fn upper_band_locomotive_cost_label(descriptor_id: u32) -> Option<&'static str> {
static LABELS: OnceLock<BTreeMap<u32, &'static str>> = OnceLock::new();
LABELS
.get_or_init(|| {
(475..=502)
.map(|descriptor_id| {
let label = format!("Upper-Band Locomotive Cost Slot {}", descriptor_id - 474);
(
descriptor_id,
Box::leak(label.into_boxed_str()) as &'static str,
)
})
.collect()
})
.get(&descriptor_id)
.copied()
}
fn recovered_locomotive_cost_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
recovered_locomotive_cost_label(descriptor_id).map(|label| {
let executable_in_runtime = recovered_locomotive_cost_loco_id(descriptor_id)
.is_some_and(|loco_id| (loco_id as usize) <= GROUNDED_LOCOMOTIVE_PREFIX.len());
RealGroupedEffectDescriptorMetadata {
descriptor_id,
label,
target_mask_bits: 0x08,
parameter_family: "locomotive_cost_scalar",
runtime_key: None,
runtime_status: if executable_in_runtime {
RealGroupedEffectRuntimeStatus::Executable
} else {
RealGroupedEffectRuntimeStatus::EvidenceBlocked
},
executable_in_runtime,
}
})
}
fn recovered_territory_access_cost_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
(descriptor_id == 453).then_some(RealGroupedEffectDescriptorMetadata {
descriptor_id,
label: "Territory Access Cost",
target_mask_bits: 0x08,
parameter_family: "territory_access_cost_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
})
}
fn recovered_locomotive_policy_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
match descriptor_id {
454 => Some(RealGroupedEffectDescriptorMetadata {
descriptor_id,
label: "All Steam Locos Avail.",
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 {
descriptor_id,
label: "All Diesel Locos Avail.",
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 {
descriptor_id,
label: "All Electric Locos Avail.",
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,
}
}
fn special_condition_world_scalar_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
let slot_index = descriptor_id.checked_sub(110)? as usize;
if slot_index != 12 {
return None;
}
let definition = KNOWN_SPECIAL_CONDITION_DEFINITIONS.get(slot_index)?;
if definition.hidden {
return None;
}
Some(RealGroupedEffectDescriptorMetadata {
descriptor_id,
label: definition.label,
target_mask_bits: 0x08,
parameter_family: "world_track_build_limit_scalar",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
})
}
fn special_condition_world_toggle_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
let slot_index = descriptor_id.checked_sub(110)? as usize;
if !(1..=34).contains(&slot_index) || matches!(slot_index, 12 | 31) {
return None;
}
let definition = KNOWN_SPECIAL_CONDITION_DEFINITIONS.get(slot_index)?;
if definition.hidden {
return None;
}
Some(RealGroupedEffectDescriptorMetadata {
descriptor_id,
label: definition.label,
target_mask_bits: 0x08,
parameter_family: "world_flag_toggle",
runtime_key: None,
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
executable_in_runtime: true,
})
}
fn classify_real_grouped_effect_semantic_family(
opcode: u8,
raw_scalar_value: i32,
value_byte_0x11: u8,
value_byte_0x12: u8,
value_word_0x14: u16,
value_word_0x16: u16,
) -> &'static str {
if opcode == 8 {
return "multivalue_scalar";
}
if value_byte_0x11 != 0 || value_byte_0x12 != 0 || value_word_0x14 != 0 || value_word_0x16 != 0
{
return "timed_duration";
}
if raw_scalar_value == 0 || raw_scalar_value == 1 {
return "bool_toggle";
}
"scalar_assignment"
}
fn classify_real_grouped_effect_row_shape(
opcode: u8,
raw_scalar_value: i32,
value_byte_0x11: u8,
value_byte_0x12: u8,
value_word_0x14: u16,
value_word_0x16: u16,
) -> &'static str {
if opcode == 8 {
return "multivalue_scalar";
}
if value_byte_0x11 != 0 || value_byte_0x12 != 0 || value_word_0x14 != 0 || value_word_0x16 != 0
{
return "timed_duration";
}
if raw_scalar_value == 0 || raw_scalar_value == 1 {
return "bool_toggle";
}
"scalar_assignment"
}
fn build_real_grouped_effect_semantic_preview(
descriptor_label: Option<&str>,
semantic_family: &str,
raw_scalar_value: i32,
value_byte_0x11: u8,
value_byte_0x12: u8,
value_word_0x14: u16,
value_word_0x16: u16,
) -> String {
let label = descriptor_label.unwrap_or("descriptor");
match semantic_family {
"bool_toggle" => {
let state = if raw_scalar_value == 0 {
"FALSE"
} else {
"TRUE"
};
format!("Set {label} to {state}")
}
"timed_duration" => format!(
"Set {label} to {raw_scalar_value} for {value_word_0x14} years {value_word_0x16} months"
),
"multivalue_scalar" => format!(
"Set {label} to {raw_scalar_value} with aux [{value_byte_0x11}, {value_byte_0x12}, {value_word_0x14}, {value_word_0x16}]"
),
_ => format!("Set {label} to {raw_scalar_value}"),
}
}
fn runtime_candidate_availability_name(label: &str) -> String {
label
.strip_suffix(" Availability")
.unwrap_or(label)
.to_string()
}
fn runtime_world_flag_key(
descriptor_metadata: RealGroupedEffectDescriptorMetadata,
) -> Option<String> {
descriptor_metadata
.runtime_key
.map(str::to_string)
.or_else(|| {
(descriptor_metadata.parameter_family == "world_flag_toggle")
.then(|| runtime_world_flag_key_from_label(descriptor_metadata.label))
})
}
pub(crate) fn runtime_world_flag_key_from_label(label: &str) -> String {
normalize_runtime_world_key(label)
}
fn runtime_world_scalar_key(
descriptor_metadata: RealGroupedEffectDescriptorMetadata,
) -> Option<String> {
descriptor_metadata
.runtime_key
.map(str::to_string)
.or_else(|| {
(descriptor_metadata.parameter_family == "world_scalar_override")
.then(|| normalize_runtime_world_key(descriptor_metadata.label))
})
}
pub(crate) fn runtime_world_scalar_key_from_label(label: &str) -> String {
normalize_runtime_world_key(label)
}
fn normalize_runtime_world_key(label: &str) -> String {
let mut key = String::with_capacity(label.len() + 6);
key.push_str("world.");
let mut last_was_underscore = false;
for ch in label.chars() {
if ch.is_ascii_alphanumeric() {
key.push(ch.to_ascii_lowercase());
last_was_underscore = false;
} else if !last_was_underscore {
key.push('_');
last_was_underscore = true;
}
}
while key.ends_with('_') {
key.pop();
}
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);
}
if row.parameter_family.as_deref() == Some("world_scalar_override") {
return Some(RealGroupedTargetSubject::WholeGame);
}
match row.target_mask_bits {
Some(0x08) => Some(RealGroupedTargetSubject::WholeGame),
Some(0x01) => Some(RealGroupedTargetSubject::Company),
Some(0x04) => Some(RealGroupedTargetSubject::Territory),
Some(0x02) => match compact_control
.grouped_scope_checkboxes_0x7ff
.get(row.group_index)
.copied()
{
Some(2) => Some(RealGroupedTargetSubject::Chairman),
_ => Some(RealGroupedTargetSubject::Player),
},
_ if row.descriptor_id == 3 => Some(RealGroupedTargetSubject::Territory),
_ if row.descriptor_id == 15
&& compact_control
.grouped_territory_selectors_0x80f
.get(row.group_index)
.is_some_and(|selector| *selector >= 0) =>
{
Some(RealGroupedTargetSubject::Territory)
}
_ => None,
}
}
fn real_grouped_target_subject_name(subject: RealGroupedTargetSubject) -> &'static str {
match subject {
RealGroupedTargetSubject::Company => "company",
RealGroupedTargetSubject::Player => "player",
RealGroupedTargetSubject::Chairman => "chairman",
RealGroupedTargetSubject::Territory => "territory",
RealGroupedTargetSubject::WholeGame => "whole_game",
}
}
fn derive_real_grouped_target_scope_name(
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
compact_control: &SmpLoadedPackedEventCompactControlSummary,
target_subject: Option<RealGroupedTargetSubject>,
target_scope_ordinal: Option<u8>,
) -> Option<String> {
match target_subject {
Some(RealGroupedTargetSubject::Company) => target_scope_ordinal
.map(real_grouped_company_scope_name)
.map(str::to_string),
Some(RealGroupedTargetSubject::Player) => target_scope_ordinal
.map(real_grouped_player_scope_name)
.map(str::to_string),
Some(RealGroupedTargetSubject::Chairman) => target_scope_ordinal
.map(real_grouped_chairman_scope_name)
.map(str::to_string),
Some(RealGroupedTargetSubject::Territory) => compact_control
.grouped_territory_selectors_0x80f
.get(row.group_index)
.copied()
.filter(|selector| *selector >= 0)
.map(|_| "specified_territories".to_string()),
Some(RealGroupedTargetSubject::WholeGame) => Some("whole_game".to_string()),
None => None,
}
}
fn real_grouped_company_scope_name(ordinal: u8) -> &'static str {
match ordinal {
0 => "condition_true_company",
1 => "selected_company",
2 => "human_companies",
3 => "ai_companies",
_ => "unsupported_company_scope",
}
}
fn real_grouped_player_scope_name(ordinal: u8) -> &'static str {
match ordinal {
0 => "condition_true_player",
1 => "selected_player",
2 => "human_players",
3 => "ai_players",
_ => "unsupported_player_scope",
}
}
fn real_grouped_chairman_scope_name(ordinal: u8) -> &'static str {
match ordinal {
0 => "condition_true_chairman",
1 => "selected_chairman",
2 => "human_chairmen",
3 => "ai_chairmen",
_ => "unsupported_chairman_scope",
}
}
fn runtime_variable_index(descriptor_id: u32) -> Option<u32> {
match descriptor_id {
39..=42 => Some(descriptor_id - 38),
43..=46 => Some(descriptor_id - 42),
47..=50 => Some(descriptor_id - 46),
51..=54 => Some(descriptor_id - 50),
_ => None,
}
}
fn decode_real_grouped_effect_actions(
grouped_effect_rows: &[SmpLoadedPackedEventGroupedEffectRowSummary],
compact_control: &SmpLoadedPackedEventCompactControlSummary,
) -> Vec<RuntimeEffect> {
grouped_effect_rows
.iter()
.filter_map(|row| decode_real_grouped_effect_action(row, compact_control))
.collect()
}
fn decode_real_grouped_effect_action(
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
compact_control: &SmpLoadedPackedEventCompactControlSummary,
) -> Option<RuntimeEffect> {
let descriptor_metadata = real_grouped_effect_descriptor_metadata(row.descriptor_id)?;
let target_scope_ordinal = compact_control
.grouped_target_scope_ordinals_0x7fb
.get(row.group_index)
.copied()?;
let target_subject = derive_real_grouped_target_subject(row, compact_control);
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.parameter_family == "runtime_variable_scalar"
&& row.row_shape == "scalar_assignment"
{
let index = runtime_variable_index(descriptor_metadata.descriptor_id)?;
return match target_subject {
Some(RealGroupedTargetSubject::WholeGame) => Some(RuntimeEffect::SetWorldVariable {
index,
value: i64::from(row.raw_scalar_value),
}),
Some(RealGroupedTargetSubject::Company) => Some(RuntimeEffect::SetCompanyVariable {
target: real_grouped_company_target(target_scope_ordinal)?,
index,
value: i64::from(row.raw_scalar_value),
}),
Some(RealGroupedTargetSubject::Player) => Some(RuntimeEffect::SetPlayerVariable {
target: real_grouped_player_target(target_scope_ordinal)?,
index,
value: i64::from(row.raw_scalar_value),
}),
Some(RealGroupedTargetSubject::Territory) => compact_control
.grouped_territory_selectors_0x80f
.get(row.group_index)
.copied()
.filter(|selector| *selector >= 0)
.map(|selector| RuntimeEffect::SetTerritoryVariable {
target: RuntimeTerritoryTarget::Ids {
ids: vec![selector as u32],
},
index,
value: i64::from(row.raw_scalar_value),
}),
_ => None,
};
}
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
&& row.row_shape == "multivalue_scalar"
{
return match target_subject {
Some(RealGroupedTargetSubject::Chairman) => Some(RuntimeEffect::SetChairmanCash {
target: real_grouped_chairman_target(target_scope_ordinal)?,
value: i64::from(row.raw_scalar_value),
}),
_ => Some(RuntimeEffect::SetPlayerCash {
target: real_grouped_player_target(target_scope_ordinal)?,
value: i64::from(row.raw_scalar_value),
}),
};
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.descriptor_id == 2
&& row.opcode == 8
&& row.row_shape == "multivalue_scalar"
{
let target = real_grouped_company_target(target_scope_ordinal)?;
return Some(RuntimeEffect::SetCompanyCash {
target,
value: i64::from(row.raw_scalar_value),
});
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.descriptor_id == 3
&& row.row_shape == "bool_toggle"
&& row.raw_scalar_value != 0
{
let target = real_grouped_company_target(target_scope_ordinal)?;
let territory = compact_control
.grouped_territory_selectors_0x80f
.get(row.group_index)
.copied()
.filter(|selector| *selector >= 0)
.map(|selector| RuntimeTerritoryTarget::Ids {
ids: vec![selector as u32],
})?;
return Some(RuntimeEffect::SetCompanyTerritoryAccess {
target,
territory,
value: true,
});
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.descriptor_id == 8
&& row.row_shape == "scalar_assignment"
{
return Some(RuntimeEffect::SetEconomicStatusCode {
value: row.raw_scalar_value,
});
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.descriptor_id == 108
&& row.row_shape == "scalar_assignment"
{
return Some(RuntimeEffect::SetSpecialCondition {
label: descriptor_metadata.label.to_string(),
value: row.raw_scalar_value as u32,
});
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.descriptor_id == 109
&& row.row_shape == "scalar_assignment"
{
return Some(RuntimeEffect::SetCandidateAvailability {
name: runtime_candidate_availability_name(descriptor_metadata.label),
value: row.raw_scalar_value as u32,
});
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.descriptor_id == 122
&& row.row_shape == "scalar_assignment"
{
return Some(RuntimeEffect::SetLimitedTrackBuildingAmount {
value: row.raw_scalar_value,
});
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.parameter_family == "world_scalar_override"
&& row.row_shape == "scalar_assignment"
{
return Some(RuntimeEffect::SetWorldScalarOverride {
key: runtime_world_scalar_key(descriptor_metadata)?,
value: i64::from(row.raw_scalar_value),
});
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.parameter_family == "cargo_price_scalar"
&& row.row_shape == "scalar_assignment"
&& row.raw_scalar_value >= 0
{
return match row.descriptor_id {
105 => Some(RuntimeEffect::SetCargoPriceOverride {
target: RuntimeCargoPriceTarget::All,
value: row.raw_scalar_value as u32,
}),
descriptor_id => grounded_named_cargo_price_label(descriptor_id).map(|name| {
RuntimeEffect::SetCargoPriceOverride {
target: RuntimeCargoPriceTarget::Named {
name: name.to_string(),
},
value: row.raw_scalar_value as u32,
}
}),
};
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.parameter_family == "cargo_production_scalar"
&& row.row_shape == "scalar_assignment"
&& row.raw_scalar_value >= 0
{
return match descriptor_metadata.descriptor_id {
177 => Some(RuntimeEffect::SetCargoProductionOverride {
target: RuntimeCargoProductionTarget::All,
value: row.raw_scalar_value as u32,
}),
178 => Some(RuntimeEffect::SetCargoProductionOverride {
target: RuntimeCargoProductionTarget::Factory,
value: row.raw_scalar_value as u32,
}),
179 => Some(RuntimeEffect::SetCargoProductionOverride {
target: RuntimeCargoProductionTarget::FarmMine,
value: row.raw_scalar_value as u32,
}),
230..=240 => {
let slot = descriptor_metadata.descriptor_id.checked_sub(229)?;
Some(RuntimeEffect::SetCargoProductionSlot {
slot,
value: row.raw_scalar_value as u32,
})
}
_ => None,
};
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.parameter_family == "territory_access_cost_scalar"
&& row.row_shape == "scalar_assignment"
&& row.raw_scalar_value >= 0
{
return Some(RuntimeEffect::SetTerritoryAccessCost {
value: row.raw_scalar_value as u32,
});
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.parameter_family == "world_flag_toggle"
&& row.row_shape == "bool_toggle"
{
return Some(RuntimeEffect::SetWorldFlag {
key: runtime_world_flag_key(descriptor_metadata)?,
value: row.raw_scalar_value != 0,
});
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.descriptor_id == 9
&& row.row_shape == "bool_toggle"
&& row.raw_scalar_value != 0
{
let target = real_grouped_company_target(target_scope_ordinal)?;
return Some(RuntimeEffect::ConfiscateCompanyAssets { target });
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.descriptor_id == 13
&& row.row_shape == "bool_toggle"
&& row.raw_scalar_value != 0
{
let target = real_grouped_company_target(target_scope_ordinal)?;
return Some(RuntimeEffect::DeactivateCompany { target });
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.descriptor_id == 14
&& row.row_shape == "bool_toggle"
&& row.raw_scalar_value != 0
{
return match target_subject {
Some(RealGroupedTargetSubject::Chairman) => Some(RuntimeEffect::DeactivateChairman {
target: real_grouped_chairman_target(target_scope_ordinal)?,
}),
_ => Some(RuntimeEffect::DeactivatePlayer {
target: real_grouped_player_target(target_scope_ordinal)?,
}),
};
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.descriptor_id == 16
&& row.row_shape == "scalar_assignment"
&& row.raw_scalar_value >= 0
{
let target = real_grouped_company_target(target_scope_ordinal)?;
return Some(RuntimeEffect::SetCompanyTrackLayingCapacity {
target,
value: Some(row.raw_scalar_value as u32),
});
}
if descriptor_metadata.executable_in_runtime
&& descriptor_metadata.descriptor_id == 15
&& row.row_shape == "bool_toggle"
&& row.raw_scalar_value != 0
{
let company_target = real_grouped_company_target(target_scope_ordinal);
let territory_target = compact_control
.grouped_territory_selectors_0x80f
.get(row.group_index)
.copied()
.filter(|selector| *selector >= 0)
.map(|selector| RuntimeTerritoryTarget::Ids {
ids: vec![selector as u32],
});
if company_target.is_none() && territory_target.is_none() {
return None;
}
return Some(RuntimeEffect::RetireTrains {
company_target,
territory_target,
locomotive_name: row.locomotive_name.clone(),
});
}
None
}
fn real_grouped_company_target(ordinal: u8) -> Option<RuntimeCompanyTarget> {
match ordinal {
0 => Some(RuntimeCompanyTarget::ConditionTrueCompany),
1 => Some(RuntimeCompanyTarget::SelectedCompany),
2 => Some(RuntimeCompanyTarget::HumanCompanies),
3 => Some(RuntimeCompanyTarget::AiCompanies),
_ => None,
}
}
fn real_grouped_player_target(ordinal: u8) -> Option<RuntimePlayerTarget> {
match ordinal {
0 => Some(RuntimePlayerTarget::ConditionTruePlayer),
1 => Some(RuntimePlayerTarget::SelectedPlayer),
2 => Some(RuntimePlayerTarget::HumanPlayers),
3 => Some(RuntimePlayerTarget::AiPlayers),
_ => None,
}
}
fn real_grouped_chairman_target(ordinal: u8) -> Option<RuntimeChairmanTarget> {
match ordinal {
0 => Some(RuntimeChairmanTarget::ConditionTrueChairman),
1 => Some(RuntimeChairmanTarget::SelectedChairman),
2 => Some(RuntimeChairmanTarget::HumanChairmen),
3 => Some(RuntimeChairmanTarget::AiChairmen),
_ => None,
}
}
fn real_grouped_chairman_target_supported_in_runtime(ordinal: u8) -> bool {
real_grouped_chairman_target(ordinal).is_some()
}
fn parse_synthetic_packed_event_action(bytes: &[u8], cursor: &mut usize) -> Option<RuntimeEffect> {
let opcode = read_u8_at(bytes, *cursor)?;
*cursor += 1;
match opcode {
0x01 => {
let key = parse_len_prefixed_string(bytes, cursor)?;
let value = read_u8_at(bytes, *cursor)? != 0;
*cursor += 1;
Some(RuntimeEffect::SetWorldFlag { key, value })
}
0x02 => {
let target = parse_synthetic_company_target(bytes, cursor)?;
let delta = read_i64_at(bytes, *cursor)?;
*cursor += 8;
Some(RuntimeEffect::AdjustCompanyCash { target, delta })
}
0x03 => {
let target = parse_synthetic_company_target(bytes, cursor)?;
let delta = read_i64_at(bytes, *cursor)?;
*cursor += 8;
Some(RuntimeEffect::AdjustCompanyDebt { target, delta })
}
0x04 => {
let name = parse_len_prefixed_string(bytes, cursor)?;
let value = read_u32_at(bytes, *cursor)?;
*cursor += 4;
Some(RuntimeEffect::SetCandidateAvailability { name, value })
}
0x05 => {
let label = parse_len_prefixed_string(bytes, cursor)?;
let value = read_u32_at(bytes, *cursor)?;
*cursor += 4;
Some(RuntimeEffect::SetSpecialCondition { label, value })
}
0x06 => {
let template_len = usize::try_from(read_u32_at(bytes, *cursor)?).ok()?;
*cursor += 4;
let template_bytes = bytes.get(*cursor..*cursor + template_len)?;
let record = parse_synthetic_event_runtime_record_template(template_bytes)?;
*cursor += template_len;
Some(RuntimeEffect::AppendEventRecord {
record: Box::new(record),
})
}
0x07 => {
let record_id = read_u32_at(bytes, *cursor)?;
*cursor += 4;
Some(RuntimeEffect::ActivateEventRecord { record_id })
}
0x08 => {
let record_id = read_u32_at(bytes, *cursor)?;
*cursor += 4;
Some(RuntimeEffect::DeactivateEventRecord { record_id })
}
0x09 => {
let record_id = read_u32_at(bytes, *cursor)?;
*cursor += 4;
Some(RuntimeEffect::RemoveEventRecord { record_id })
}
_ => None,
}
}
fn parse_synthetic_event_runtime_record_template(
bytes: &[u8],
) -> Option<RuntimeEventRecordTemplate> {
if !bytes.starts_with(PACKED_EVENT_RECORD_TEMPLATE_SYNTHETIC_MAGIC) {
return None;
}
let mut cursor = PACKED_EVENT_RECORD_TEMPLATE_SYNTHETIC_MAGIC.len();
let record_id = read_u32_at(bytes, cursor)?;
cursor += 4;
let trigger_kind = read_u8_at(bytes, cursor)?;
cursor += 1;
let flags = read_u8_at(bytes, cursor)?;
cursor += 1;
let action_count = usize::from(read_u8_at(bytes, cursor)?);
cursor += 1;
cursor += 1;
let mut effects = Vec::with_capacity(action_count);
for _ in 0..action_count {
effects.push(parse_synthetic_packed_event_action(bytes, &mut cursor)?);
}
if cursor != bytes.len() {
return None;
}
Some(RuntimeEventRecordTemplate {
record_id,
trigger_kind,
active: flags & 0x01 != 0,
marks_collection_dirty: flags & 0x02 != 0,
one_shot: flags & 0x04 != 0,
conditions: Vec::new(),
effects,
})
}
fn parse_synthetic_company_target(
bytes: &[u8],
cursor: &mut usize,
) -> Option<RuntimeCompanyTarget> {
let target_kind = read_u8_at(bytes, *cursor)?;
*cursor += 1;
match target_kind {
0x00 => Some(RuntimeCompanyTarget::AllActive),
0x01 => {
let count = usize::from(read_u8_at(bytes, *cursor)?);
*cursor += 1;
let mut ids = Vec::with_capacity(count);
for _ in 0..count {
ids.push(read_u32_at(bytes, *cursor)?);
*cursor += 4;
}
Some(RuntimeCompanyTarget::Ids { ids })
}
_ => None,
}
}
fn parse_len_prefixed_string(bytes: &[u8], cursor: &mut usize) -> Option<String> {
let len = usize::from(read_u8_at(bytes, *cursor)?);
*cursor += 1;
let text_bytes = bytes.get(*cursor..*cursor + len)?;
*cursor += len;
Some(String::from_utf8_lossy(text_bytes).into_owned())
}
fn parse_optional_u16_len_prefixed_string(
bytes: &[u8],
cursor: &mut usize,
) -> Option<Option<String>> {
let len = usize::from(read_u16_at(bytes, *cursor)?);
*cursor += 2;
if len == 0 {
return Some(None);
}
let text_bytes = bytes.get(*cursor..*cursor + len)?;
*cursor += len;
Some(Some(String::from_utf8_lossy(text_bytes).into_owned()))
}
fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
match effect {
RuntimeEffect::SetChairmanCash { target, .. }
| RuntimeEffect::DeactivateChairman { target } => matches!(
target,
RuntimeChairmanTarget::AllActive
| RuntimeChairmanTarget::HumanChairmen
| RuntimeChairmanTarget::AiChairmen
| RuntimeChairmanTarget::SelectedChairman
| RuntimeChairmanTarget::ConditionTrueChairman
| RuntimeChairmanTarget::Ids { .. }
),
RuntimeEffect::SetWorldFlag { .. }
| RuntimeEffect::SetWorldVariable { .. }
| RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
| RuntimeEffect::SetEconomicStatusCode { .. }
| RuntimeEffect::SetCompanyGovernanceScalar { .. }
| RuntimeEffect::SetCandidateAvailability { .. }
| RuntimeEffect::SetNamedLocomotiveAvailability { .. }
| RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. }
| RuntimeEffect::SetNamedLocomotiveCost { .. }
| RuntimeEffect::SetCargoPriceOverride { .. }
| RuntimeEffect::SetCargoProductionOverride { .. }
| RuntimeEffect::SetCargoProductionSlot { .. }
| RuntimeEffect::SetWorldScalarOverride { .. }
| RuntimeEffect::SetTerritoryAccessCost { .. }
| RuntimeEffect::SetSpecialCondition { .. }
| RuntimeEffect::ConfiscateCompanyAssets { .. }
| RuntimeEffect::DeactivateCompany { .. }
| RuntimeEffect::DeactivatePlayer { .. }
| RuntimeEffect::SetCompanyTrackLayingCapacity { .. }
| RuntimeEffect::RetireTrains { .. }
| RuntimeEffect::ActivateEventRecord { .. }
| RuntimeEffect::DeactivateEventRecord { .. }
| RuntimeEffect::RemoveEventRecord { .. } => true,
RuntimeEffect::SetPlayerCash { target, .. }
| RuntimeEffect::SetPlayerVariable { target, .. } => matches!(
target,
RuntimePlayerTarget::AllActive
| RuntimePlayerTarget::Ids { .. }
| RuntimePlayerTarget::HumanPlayers
| RuntimePlayerTarget::AiPlayers
| RuntimePlayerTarget::SelectedPlayer
| RuntimePlayerTarget::ConditionTruePlayer
),
RuntimeEffect::SetCompanyTerritoryAccess {
target, territory, ..
} => {
matches!(
target,
RuntimeCompanyTarget::AllActive
| RuntimeCompanyTarget::Ids { .. }
| RuntimeCompanyTarget::HumanCompanies
| RuntimeCompanyTarget::AiCompanies
| RuntimeCompanyTarget::SelectedCompany
| RuntimeCompanyTarget::ConditionTrueCompany
) && matches!(
territory,
RuntimeTerritoryTarget::AllTerritories | RuntimeTerritoryTarget::Ids { .. }
)
}
RuntimeEffect::SetCompanyCash { target, .. }
| RuntimeEffect::SetCompanyVariable { target, .. }
| RuntimeEffect::AdjustCompanyCash { target, .. }
| RuntimeEffect::AdjustCompanyDebt { target, .. } => matches!(
target,
RuntimeCompanyTarget::AllActive
| RuntimeCompanyTarget::Ids { .. }
| RuntimeCompanyTarget::HumanCompanies
| RuntimeCompanyTarget::AiCompanies
| RuntimeCompanyTarget::SelectedCompany
| RuntimeCompanyTarget::ConditionTrueCompany
),
RuntimeEffect::SetTerritoryVariable { target, .. } => matches!(
target,
RuntimeTerritoryTarget::AllTerritories | RuntimeTerritoryTarget::Ids { .. }
),
RuntimeEffect::AppendEventRecord { record } => record
.effects
.iter()
.all(runtime_effect_supported_for_save_import),
}
}
fn runtime_condition_supported_for_save_import(condition: &RuntimeCondition) -> bool {
match condition {
RuntimeCondition::WorldVariableThreshold { .. }
| RuntimeCondition::CompanyNumericThreshold { .. }
| RuntimeCondition::CompanyVariableThreshold { .. }
| RuntimeCondition::PlayerVariableThreshold { .. }
| RuntimeCondition::ChairmanNumericThreshold { .. }
| RuntimeCondition::TerritoryNumericThreshold { .. }
| RuntimeCondition::TerritoryVariableThreshold { .. }
| RuntimeCondition::CompanyTerritoryNumericThreshold { .. }
| RuntimeCondition::SpecialConditionThreshold { .. }
| RuntimeCondition::CandidateAvailabilityThreshold { .. }
| RuntimeCondition::NamedLocomotiveAvailabilityThreshold { .. }
| RuntimeCondition::NamedLocomotiveCostThreshold { .. }
| RuntimeCondition::CargoProductionSlotThreshold { .. }
| RuntimeCondition::CargoProductionTotalThreshold { .. }
| RuntimeCondition::FactoryProductionTotalThreshold { .. }
| RuntimeCondition::FarmMineProductionTotalThreshold { .. }
| RuntimeCondition::OtherCargoProductionTotalThreshold { .. }
| RuntimeCondition::LimitedTrackBuildingAmountThreshold { .. }
| RuntimeCondition::TerritoryAccessCostThreshold { .. }
| RuntimeCondition::EconomicStatusCodeThreshold { .. }
| RuntimeCondition::WorldFlagEquals { .. } => true,
}
}
fn build_unsupported_event_runtime_record_summaries(
live_entry_ids: &[u32],
note: &str,
) -> Vec<SmpLoadedPackedEventRecordSummary> {
live_entry_ids
.iter()
.copied()
.enumerate()
.map(
|(record_index, live_entry_id)| SmpLoadedPackedEventRecordSummary {
record_index,
live_entry_id,
payload_offset: None,
payload_len: None,
decode_status: "unsupported_framing".to_string(),
payload_family: "unsupported_framing".to_string(),
trigger_kind: None,
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![0, 0, 0, 0],
grouped_effect_rows: Vec::new(),
decoded_conditions: Vec::new(),
decoded_actions: Vec::new(),
executable_import_ready: false,
notes: vec![note.to_string()],
},
)
.collect()
}
fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> SmpInspectionReport {
let known_tag_hits = KNOWN_TAG_DEFINITIONS
.iter()
.filter_map(|definition| {
let offsets = find_u16_le_offsets(bytes, definition.tag_id);
if offsets.is_empty() {
return None;
}
Some(SmpKnownTagHit {
tag_id: definition.tag_id,
tag_hex: format!("0x{:04x}", definition.tag_id),
label: definition.label.to_string(),
grounded_meaning: definition.grounded_meaning.to_string(),
hit_count: offsets.len(),
sample_offsets: offsets
.iter()
.copied()
.take(TAG_OFFSET_SAMPLE_LIMIT)
.collect(),
last_offset: offsets.last().copied(),
})
})
.collect::<Vec<_>>();
let shared_header = parse_shared_header(bytes);
let header_variant_probe = shared_header.as_ref().map(classify_header_variant_probe);
let first_ascii_run = find_first_ascii_run(bytes);
let early_content_probe = first_ascii_run
.as_ref()
.and_then(|ascii_run| probe_early_content_layout(bytes, ascii_run));
let secondary_variant_probe = early_content_probe
.as_ref()
.and_then(classify_secondary_variant_probe);
let container_profile = classify_container_profile(
file_extension_hint.as_deref(),
header_variant_probe.as_ref(),
secondary_variant_probe.as_ref(),
);
let runtime_anchor_cycle_block = parse_runtime_anchor_cycle_block(
bytes,
container_profile.as_ref(),
secondary_variant_probe.as_ref(),
);
let save_bootstrap_block =
parse_save_bootstrap_block(container_profile.as_ref(), secondary_variant_probe.as_ref());
let save_anchor_run_block = parse_save_anchor_run_block(
bytes,
container_profile.as_ref(),
save_bootstrap_block.as_ref(),
);
let runtime_trailer_block = parse_runtime_trailer_block(
container_profile.as_ref(),
runtime_anchor_cycle_block.as_ref(),
);
let runtime_post_span_probe =
parse_runtime_post_span_probe(bytes, runtime_trailer_block.as_ref());
let rt3_105_packed_profile_probe = parse_rt3_105_packed_profile_probe(
bytes,
file_extension_hint.as_deref(),
header_variant_probe.as_ref(),
container_profile.as_ref(),
);
let rt3_105_post_span_bridge_probe = parse_rt3_105_post_span_bridge_probe(
runtime_trailer_block.as_ref(),
runtime_post_span_probe.as_ref(),
rt3_105_packed_profile_probe.as_ref(),
);
let rt3_105_save_bridge_payload_probe =
parse_rt3_105_save_bridge_payload_probe(bytes, rt3_105_post_span_bridge_probe.as_ref());
let save_world_selection_context_probe = parse_save_world_selection_context_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
);
let save_world_issue_37_probe = parse_save_world_issue_37_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
);
let save_world_economic_tuning_probe = parse_save_world_economic_tuning_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
);
let save_world_finance_neighborhood_probe = parse_save_world_finance_neighborhood_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
);
let save_company_collection_header_probe = parse_save_company_collection_header_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
);
let save_chairman_profile_collection_header_probe =
parse_save_chairman_profile_collection_header_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
);
let save_region_collection_header_probe = parse_save_region_collection_header_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
);
let save_region_collection_directory_probe = parse_save_region_collection_directory_probe(
bytes,
save_region_collection_header_probe.as_ref(),
);
let save_placed_structure_collection_header_probe =
parse_save_placed_structure_collection_header_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
);
let save_company_roster_probe = parse_save_company_roster_probe(
bytes,
save_company_collection_header_probe.as_ref(),
save_world_selection_context_probe.as_ref(),
);
let save_chairman_profile_table_probe = parse_save_chairman_profile_table_probe(
bytes,
save_chairman_profile_collection_header_probe.as_ref(),
save_world_selection_context_probe.as_ref(),
save_company_collection_header_probe.as_ref(),
);
let rt3_105_save_name_table_probe = parse_rt3_105_save_name_table_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
rt3_105_save_bridge_payload_probe.as_ref(),
);
let rt3_105_save_named_locomotive_availability_probe =
parse_rt3_105_save_named_locomotive_availability_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
rt3_105_packed_profile_probe.as_ref(),
);
let special_conditions_probe = parse_special_conditions_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
);
let smp_aligned_runtime_rule_band_probe = parse_smp_aligned_runtime_rule_band_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
special_conditions_probe.as_ref(),
);
let post_special_conditions_scalar_probe = parse_post_special_conditions_scalar_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
special_conditions_probe.as_ref(),
);
let post_text_field_neighborhood_probe = parse_post_text_field_neighborhood_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
special_conditions_probe.as_ref(),
);
let locomotive_policy_neighborhood_probe = parse_locomotive_policy_neighborhood_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
special_conditions_probe.as_ref(),
);
let pre_recipe_scalar_plateau_probe = parse_pre_recipe_scalar_plateau_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
special_conditions_probe.as_ref(),
);
let recipe_book_summary_probe = parse_recipe_book_summary_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
special_conditions_probe.as_ref(),
);
let classic_rehydrate_profile_probe =
parse_classic_rehydrate_profile_probe(bytes, runtime_post_span_probe.as_ref());
let save_load_summary = build_save_load_summary(
file_extension_hint.as_deref(),
container_profile.as_ref(),
runtime_trailer_block.as_ref(),
rt3_105_post_span_bridge_probe.as_ref(),
classic_rehydrate_profile_probe.as_ref(),
rt3_105_packed_profile_probe.as_ref(),
rt3_105_save_name_table_probe.as_ref(),
);
let event_runtime_collection_summary = parse_event_runtime_collection_summary(
bytes,
container_profile.as_ref(),
save_load_summary.as_ref(),
);
let mut warnings = Vec::new();
if bytes.is_empty() {
warnings
.push("File is empty, so no `.smp` container structure could be observed.".to_string());
}
if known_tag_hits.is_empty() {
warnings.push(
"No grounded runtime bundle tags were found in little-endian form. This does not prove the file is invalid."
.to_string(),
);
}
if shared_header.is_none() && !bytes.is_empty() {
warnings.push(
"File is shorter than the observed 64-byte common RT3 bundle preamble.".to_string(),
);
}
if let Some(shared_header) = &shared_header {
let header_family_is_known = header_variant_probe
.as_ref()
.map(|probe| probe.is_known_family)
.unwrap_or(false);
if !shared_header.matches_grounded_common_signature && !header_family_is_known {
warnings.push(
"The first 64-byte preamble does not match the currently observed shared RT3 bundle signature."
.to_string(),
);
}
}
if first_ascii_run.is_some() && early_content_probe.is_none() {
warnings.push(
"Found early text content but could not resolve the next stable nonzero region after its zero padding."
.to_string(),
);
}
if container_profile
.as_ref()
.is_some_and(|profile| !profile.is_known_profile)
{
warnings.push(
"The current probes did not match any known composite container profile.".to_string(),
);
}
if known_tag_hits
.iter()
.any(|hit| hit.hit_count > hit.sample_offsets.len())
{
warnings.push(
"Known-tag offsets are sampled in this report. Large hit counts usually mean byte-pattern noise, not validated chunk boundaries."
.to_string(),
);
}
warnings.push(
"Inspection scans raw bytes for a small grounded tag set only. It does not validate bundle layout or decode payloads."
.to_string(),
);
SmpInspectionReport {
inspection_mode: "grounded-tag-scan-plus-preamble".to_string(),
file_extension_hint,
file_size: bytes.len(),
sha256: sha256_hex(bytes),
preamble: parse_preamble(bytes),
shared_header,
header_variant_probe,
first_ascii_run,
early_content_probe,
secondary_variant_probe,
container_profile,
save_bootstrap_block,
save_anchor_run_block,
runtime_anchor_cycle_block,
runtime_trailer_block,
runtime_post_span_probe,
rt3_105_post_span_bridge_probe,
rt3_105_save_bridge_payload_probe,
save_world_selection_context_probe,
save_world_issue_37_probe,
save_world_economic_tuning_probe,
save_world_finance_neighborhood_probe,
save_company_collection_header_probe,
save_chairman_profile_collection_header_probe,
save_region_collection_header_probe,
save_region_collection_directory_probe,
save_placed_structure_collection_header_probe,
save_company_roster_probe,
save_chairman_profile_table_probe,
rt3_105_save_name_table_probe,
rt3_105_save_named_locomotive_availability_probe,
special_conditions_probe,
smp_aligned_runtime_rule_band_probe,
post_special_conditions_scalar_probe,
post_text_field_neighborhood_probe,
locomotive_policy_neighborhood_probe,
pre_recipe_scalar_plateau_probe,
recipe_book_summary_probe,
classic_rehydrate_profile_probe,
rt3_105_packed_profile_probe,
save_load_summary,
event_runtime_collection_summary,
contains_grounded_runtime_tags: !known_tag_hits.is_empty(),
known_tag_hits,
notes: vec![
"Grounded `.smp` runtime tags currently include mask-plane payload ids 0x2cee and 0x2d51.".to_string(),
"Grounded sidecar-byte-plane bundle family currently spans 0x9471..0x9472.".to_string(),
"The shared-header parse is intentionally conservative: it only names common preamble lanes and checks the observed RT3 bundle-family signature.".to_string(),
"The header-variant probe classifies the preamble into one of the currently observed install-era families when possible."
.to_string(),
"The early-content probe resolves the first stable nonzero block after the padded scenario text and then captures the next aligned word window."
.to_string(),
"The secondary-variant probe classifies that aligned word window into one of the currently observed file-family patterns."
.to_string(),
"The recipe-book summary probe reports per-book structural signatures at the grounded recipe-book root [world+0x0fe7] without attempting a full cargo-line decode."
.to_string(),
"Where a recipe cargo-token word looks like two printable letters in its high 16 bits, the probe exposes that as one probable ASCII stem while still treating the wider token semantics as inferred."
.to_string(),
"The container-profile layer combines extension hint, header family, and second-window family into one observed container classification."
.to_string(),
"The save-bootstrap reader currently parses one conservative 8-word descriptor only for known save-container profiles."
.to_string(),
"The save-anchor-run reader follows that descriptor tail into the observed repeated 9-word anchor cycle and captures the first trailer words after the cycle diverges."
.to_string(),
"The runtime-anchor-cycle reader applies the same cycle/trailer scan across the currently known save and sandbox runtime container profiles."
.to_string(),
"The runtime-trailer reader classifies the first 16 words after the cycle divergence into the currently observed runtime trailer families."
.to_string(),
"The runtime post-span probe follows the trailer's high-16 span lane into the later file region and records the next nonzero bytes, the first aligned high-16-dense candidate window, and any grounded progress-id hits found nearby."
.to_string(),
"The RT3 1.05 post-span bridge probe correlates the trailer selector/descriptor lanes with the next candidate region and the later packed-profile block for the currently observed 1.05 save families."
.to_string(),
"The RT3 1.05 common-save bridge payload probe captures the two stable bridge-stage blocks currently observed under the base 1.05 save branch."
.to_string(),
"The RT3 1.05 candidate-availability table probe decodes the fixed-width trailing name table from either the common-save bridge payload or the fixed 0x6a70..0x73c0 source range when that header validates."
.to_string(),
"The RT3 1.05 save-side named locomotive availability probe scans the post-profile save region for the grounded fixed-width locomotive-name-plus-dword row family when that run is present."
.to_string(),
"The post-special-conditions scalar probe captures the fixed 0x0df4..0x0f30 dword window immediately after the hidden sentinel slot, splits it into the aligned-band overlap prefix and the later tail, and records the live-object offset alignment of that tail without claiming a byte-for-byte mirror."
.to_string(),
"The classic rehydrate-profile probe recognizes the grounded 0x32dc -> 0x3714 -> 0x3715 progress-id sequence and captures the exact 0x108-byte block between the latter two ids when that pattern appears."
.to_string(),
"The classic packed-profile block reader exposes the stable map-path, display-name, atlas-tracked latch bytes, and the small set of nonzero word lanes observed inside that 0x108-byte block."
.to_string(),
"The RT3 1.05 packed-profile probe recognizes the later string-bearing save block rooted at the first post-header .gmp path and exposes the observed map-path, display-name, atlas-tracked byte lanes, and stable nonzero words."
.to_string(),
format!(
"Restore-side loading of the four sidecar byte planes is only grounded for bundle versions >= 0x{:04x}.",
SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION
),
],
warnings,
}
}
fn build_save_load_summary(
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
runtime_trailer_block: Option<&SmpRuntimeTrailerBlock>,
rt3_105_post_span_bridge_probe: Option<&SmpRt3105PostSpanBridgeProbe>,
classic_rehydrate_profile_probe: Option<&SmpClassicRehydrateProfileProbe>,
rt3_105_packed_profile_probe: Option<&SmpRt3105PackedProfileProbe>,
rt3_105_save_name_table_probe: Option<&SmpRt3105SaveNameTableProbe>,
) -> Option<SmpSaveLoadSummary> {
let file_extension_hint = file_extension_hint.map(str::to_string);
let container_profile_family = container_profile.map(|profile| profile.profile_family.clone());
let trailer_family = runtime_trailer_block.map(|trailer| trailer.trailer_family.clone());
let bridge_family = rt3_105_post_span_bridge_probe.map(|bridge| bridge.bridge_family.clone());
let candidate_table =
rt3_105_save_name_table_probe.map(|probe| SmpSaveLoadCandidateTableSummary {
source_kind: probe.source_kind.clone(),
semantic_family: probe.semantic_family.clone(),
observed_entry_count: probe.observed_entry_count,
zero_availability_count: probe.zero_trailer_entry_count,
zero_availability_names: probe.zero_trailer_entry_names.clone(),
footer_progress_hex_words: vec![
probe.footer_progress_word_0_hex.clone(),
probe.footer_progress_word_1_hex.clone(),
],
});
if let Some(probe) = classic_rehydrate_profile_probe {
let block = &probe.packed_profile_block;
let mut notes = vec![
"Classic save load reaches the grounded late rehydrate band 0x32dc -> 0x3714 -> 0x3715."
.to_string(),
"The file exposes one exact 0x108 packed-profile block between progress ids 0x3714 and 0x3715."
.to_string(),
];
if let Some(map_path) = &block.map_path {
notes.push(format!("Packed profile map path: {map_path}"));
}
if let Some(display_name) = &block.display_name {
notes.push(format!("Packed profile display name: {display_name}"));
}
return Some(SmpSaveLoadSummary {
file_extension_hint,
container_profile_family,
mechanism_family: "classic-save-rehydrate-v1".to_string(),
mechanism_confidence: "grounded".to_string(),
packed_profile_kind: Some("classic-rehydrate-profile".to_string()),
packed_profile_family: Some(probe.profile_family.clone()),
packed_profile_offset: Some(probe.packed_profile_offset),
packed_profile_len: Some(probe.packed_profile_len),
map_path: block.map_path.clone(),
display_name: block.display_name.clone(),
profile_byte_0x77: Some(block.profile_byte_0x77),
profile_byte_0x77_hex: Some(block.profile_byte_0x77_hex.clone()),
profile_byte_0x82: Some(block.profile_byte_0x82),
profile_byte_0x82_hex: Some(block.profile_byte_0x82_hex.clone()),
profile_byte_0x97: Some(block.profile_byte_0x97),
profile_byte_0x97_hex: Some(block.profile_byte_0x97_hex.clone()),
profile_byte_0xc5: Some(block.profile_byte_0xc5),
profile_byte_0xc5_hex: Some(block.profile_byte_0xc5_hex.clone()),
trailer_family,
bridge_family: None,
candidate_table,
notes,
});
}
if let Some(probe) = rt3_105_packed_profile_probe {
let block = &probe.packed_profile_block;
let mechanism_family = rt3_105_post_span_bridge_probe
.map(|bridge| bridge.bridge_family.clone())
.unwrap_or_else(|| match probe.profile_family.as_str() {
"rt3-105-scenario-save-container-v1" => {
"rt3-105-scenario-save-profile-analog-v1".to_string()
}
"rt3-105-alt-save-container-v1" => "rt3-105-alt-save-profile-analog-v1".to_string(),
_ => "rt3-105-save-profile-analog-v1".to_string(),
});
let mechanism_confidence = if rt3_105_post_span_bridge_probe.is_some() {
"mixed"
} else {
"inferred"
}
.to_string();
let mut notes = Vec::new();
if let Some(bridge) = rt3_105_post_span_bridge_probe {
notes.push(format!(
"RT3 1.05 save branch uses {} with selector/descriptor {} -> {}.",
bridge.bridge_family, bridge.selector_high_hex, bridge.descriptor_high_hex
));
} else {
notes.push(
"RT3 1.05 save exposes a packed-profile analogue, but the upstream load bridge is not resolved for this branch."
.to_string(),
);
}
if let Some(map_path) = &block.map_path {
notes.push(format!("Packed profile map path: {map_path}"));
}
if let Some(display_name) = &block.display_name {
notes.push(format!("Packed profile display name: {display_name}"));
}
if let Some(table) = &candidate_table {
notes.push(format!(
"Candidate table source {} carries {} entries with {} zero-availability overrides.",
table.source_kind, table.observed_entry_count, table.zero_availability_count
));
}
return Some(SmpSaveLoadSummary {
file_extension_hint,
container_profile_family,
mechanism_family,
mechanism_confidence,
packed_profile_kind: Some("rt3-105-packed-profile".to_string()),
packed_profile_family: Some(probe.profile_family.clone()),
packed_profile_offset: Some(probe.packed_profile_offset),
packed_profile_len: Some(probe.packed_profile_len),
map_path: block.map_path.clone(),
display_name: block.display_name.clone(),
profile_byte_0x77: Some(block.profile_byte_0x77),
profile_byte_0x77_hex: Some(block.profile_byte_0x77_hex.clone()),
profile_byte_0x82: Some(block.profile_byte_0x82),
profile_byte_0x82_hex: Some(block.profile_byte_0x82_hex.clone()),
profile_byte_0x97: Some(block.profile_byte_0x97),
profile_byte_0x97_hex: Some(block.profile_byte_0x97_hex.clone()),
profile_byte_0xc5: Some(block.profile_byte_0xc5),
profile_byte_0xc5_hex: Some(block.profile_byte_0xc5_hex.clone()),
trailer_family,
bridge_family,
candidate_table,
notes,
});
}
if let Some(table) = candidate_table {
return Some(SmpSaveLoadSummary {
file_extension_hint,
container_profile_family,
mechanism_family: "rt3-105-candidate-catalog-source-v1".to_string(),
mechanism_confidence: "mixed".to_string(),
packed_profile_kind: None,
packed_profile_family: None,
packed_profile_offset: None,
packed_profile_len: None,
map_path: None,
display_name: None,
profile_byte_0x77: None,
profile_byte_0x77_hex: None,
profile_byte_0x82: None,
profile_byte_0x82_hex: None,
profile_byte_0x97: None,
profile_byte_0x97_hex: None,
profile_byte_0xc5: None,
profile_byte_0xc5_hex: None,
trailer_family,
bridge_family,
notes: vec![
format!(
"The file carries the shared 1.05 candidate table source block through {}.",
table.source_kind
),
format!(
"The table exposes {} named entries with {} zero-availability overrides.",
table.observed_entry_count, table.zero_availability_count
),
],
candidate_table: Some(table),
});
}
None
}
fn parse_preamble(bytes: &[u8]) -> SmpPreamble {
let byte_len = bytes.len().min(PREAMBLE_U32_WORD_COUNT * 4);
let words = bytes[..byte_len]
.chunks_exact(4)
.enumerate()
.map(|(index, chunk)| {
let value_le = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
SmpPreambleWord {
index,
offset: index * 4,
value_le,
value_hex: format!("0x{value_le:08x}"),
}
})
.collect::<Vec<_>>();
SmpPreamble {
byte_len,
word_count: words.len(),
words,
}
}
fn parse_shared_header(bytes: &[u8]) -> Option<SmpSharedHeader> {
let words = read_preamble_words(bytes)?;
let shared_signature_words_1_to_7 = words[1..=7].to_vec();
let payload_window_words_8_to_9 = words[8..=9].to_vec();
let reserved_words_10_to_14 = words[10..=14].to_vec();
let final_flag_word = words[15];
Some(SmpSharedHeader {
byte_len: PREAMBLE_U32_WORD_COUNT * 4,
root_kind_word: words[0],
root_kind_word_hex: format!("0x{:08x}", words[0]),
primary_family_tag: words[1],
primary_family_tag_hex: format!("0x{:08x}", words[1]),
shared_signature_hex_words_1_to_7: shared_signature_words_1_to_7
.iter()
.map(|word| format!("0x{word:08x}"))
.collect(),
matches_grounded_common_signature: shared_signature_words_1_to_7
== SHARED_SIGNATURE_WORDS_1_TO_7,
shared_signature_words_1_to_7,
payload_window_hex_words_8_to_9: payload_window_words_8_to_9
.iter()
.map(|word| format!("0x{word:08x}"))
.collect(),
payload_window_words_8_to_9,
reserved_words_10_to_14_all_zero: reserved_words_10_to_14.iter().all(|word| *word == 0),
reserved_words_10_to_14,
final_flag_word,
final_flag_word_hex: format!("0x{final_flag_word:08x}"),
})
}
fn classify_header_variant_probe(shared_header: &SmpSharedHeader) -> SmpHeaderVariantProbe {
let words = &shared_header.shared_signature_words_1_to_7;
let root = shared_header.root_kind_word;
let final_flag = shared_header.final_flag_word;
let (variant_family, evidence, is_known_family) = match (root, words.as_slice(), final_flag) {
(
0x00002649,
[
0x00002ee0,
0x00040001,
0x00028000,
0x00010000,
0x00000771,
0x00000771,
0x00000771,
],
0x00000001,
) => (
"rt3-105-gmx-header-v1".to_string(),
vec![
"root kind word 0x00002649".to_string(),
"1.05 common signature words 1..7".to_string(),
"final flag 0x00000001".to_string(),
],
true,
),
(
0x000025e5,
[
0x00002ee0,
0x00040001,
0x00028000,
0x00010000,
0x00000771,
0x00000771,
0x00000771,
],
0x00000000,
) => (
"rt3-105-common-header-v1".to_string(),
vec![
"root kind word 0x000025e5".to_string(),
"1.05 common signature words 1..7".to_string(),
"final flag 0x00000000".to_string(),
],
true,
),
(
0x000025e5,
[
0x00002ee0,
0x00040001,
0x00018000,
0x00010000,
0x00000746,
0x00000746,
0x00000746,
],
0x00000000,
) => (
"rt3-105-scenario-save-header-v1".to_string(),
vec![
"root kind word 0x000025e5".to_string(),
"1.05 scenario-save signature words 1..7".to_string(),
"final flag 0x00000000".to_string(),
],
true,
),
(
0x000025e5,
[
0x00002ee0,
0x0001c001,
0x00018000,
0x00010000,
0x00000754,
0x00000754,
0x00000754,
],
0x00000000,
) => (
"rt3-105-alt-save-header-v1".to_string(),
vec![
"root kind word 0x000025e5".to_string(),
"1.05 alternate-save signature words 1..7".to_string(),
"final flag 0x00000000".to_string(),
],
true,
),
(
0x000026ad,
[
0x00002ee0,
0x00014001,
0x00020000,
0x00010000,
0x00000725,
0x00000725,
0x00000725,
],
0x00000100,
) => (
"rt3-classic-gms-header-v1".to_string(),
vec![
"root kind word 0x000026ad".to_string(),
"classic save signature words 1..7".to_string(),
"final flag 0x00000100".to_string(),
],
true,
),
(
0x000026ad,
[
0x00002ee0,
0x0001c001,
0x00018000,
0x00010000,
0x00000765,
0x00000765,
0x00000765,
],
0x00000001,
) => (
"rt3-classic-gmx-header-v1".to_string(),
vec![
"root kind word 0x000026ad".to_string(),
"classic sandbox signature words 1..7".to_string(),
"final flag 0x00000001".to_string(),
],
true,
),
(0x000025e5, [0x00002ee0, _, _, 0x00010000, _, _, _], 0x00000000 | 0x00000100) => (
"rt3-map-header-family".to_string(),
vec![
"root kind word 0x000025e5".to_string(),
"map-family anchor 0x00002ee0".to_string(),
"word4 0x00010000".to_string(),
],
true,
),
_ => (
"unknown".to_string(),
vec![format!(
"root=0x{root:08x}, words1..7={}, final=0x{final_flag:08x}",
words
.iter()
.map(|word| format!("0x{word:08x}"))
.collect::<Vec<_>>()
.join(", ")
)],
false,
),
};
SmpHeaderVariantProbe {
variant_family,
variant_evidence: evidence,
is_known_family,
}
}
fn read_preamble_words(bytes: &[u8]) -> Option<[u32; PREAMBLE_U32_WORD_COUNT]> {
if bytes.len() < PREAMBLE_U32_WORD_COUNT * 4 {
return None;
}
let mut words = [0u32; PREAMBLE_U32_WORD_COUNT];
for (index, chunk) in bytes[..PREAMBLE_U32_WORD_COUNT * 4]
.chunks_exact(4)
.enumerate()
{
words[index] = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
}
Some(words)
}
fn probe_early_content_layout(
bytes: &[u8],
ascii_run: &SmpAsciiPreview,
) -> Option<SmpEarlyContentProbe> {
let search_start = ascii_run.offset + ascii_run.byte_len;
let first_post_text_nonzero_offset = find_next_nonzero_offset(bytes, search_start)?;
let zero_pad_after_text_len = first_post_text_nonzero_offset.saturating_sub(search_start);
let first_zero_run_after_block = find_zero_run(
bytes,
first_post_text_nonzero_offset,
EARLY_ZERO_RUN_THRESHOLD,
)
.unwrap_or(bytes.len());
let first_post_text_block = &bytes[first_post_text_nonzero_offset..first_zero_run_after_block];
let secondary_nonzero_offset = find_next_nonzero_offset(bytes, first_zero_run_after_block);
let trailing_zero_pad_after_first_block_len = secondary_nonzero_offset
.map(|offset| offset.saturating_sub(first_zero_run_after_block))
.unwrap_or_else(|| bytes.len().saturating_sub(first_zero_run_after_block));
let secondary_aligned_word_window_offset = secondary_nonzero_offset.map(|offset| offset & !0x3);
let secondary_aligned_word_window_words = secondary_aligned_word_window_offset
.map(|offset| read_u32_window(bytes, offset, EARLY_ALIGNED_WORD_WINDOW_COUNT))
.unwrap_or_default();
let secondary_preview_hex = secondary_nonzero_offset
.map(|offset| {
hex_encode(&bytes[offset..bytes.len().min(offset + EARLY_PREVIEW_BYTE_LIMIT)])
})
.unwrap_or_default();
Some(SmpEarlyContentProbe {
first_post_text_nonzero_offset,
zero_pad_after_text_len,
first_post_text_block_len: first_post_text_block.len(),
first_post_text_block_hex: hex_encode(first_post_text_block),
trailing_zero_pad_after_first_block_len,
secondary_nonzero_offset,
secondary_aligned_word_window_offset,
secondary_aligned_word_window_hex_words: secondary_aligned_word_window_words
.iter()
.map(|word| format!("0x{word:08x}"))
.collect(),
secondary_aligned_word_window_words,
secondary_preview_hex,
})
}
fn classify_secondary_variant_probe(
probe: &SmpEarlyContentProbe,
) -> Option<SmpSecondaryVariantProbe> {
let aligned_window_offset = probe.secondary_aligned_word_window_offset?;
let words = probe.secondary_aligned_word_window_words.clone();
if words.is_empty() {
return None;
}
let mut evidence = Vec::new();
let variant_family = match words.as_slice() {
[0x001e0000, 0x86a00100, 0x03000001, 0xf0000100, ..] => {
evidence.push("leading word 0x001e0000".to_string());
evidence.push("anchor word 0x86a00100".to_string());
evidence.push("third/fourth words 0x03000001 and 0xf0000100".to_string());
"rt3-gms-family-v1".to_string()
}
[0x000a0000, 0x49f00100, 0x00000002, 0xa0000000, ..] => {
evidence.push("leading word 0x000a0000".to_string());
evidence.push("anchor word 0x49f00100".to_string());
evidence.push("third/fourth words 0x00000002 and 0xa0000000".to_string());
"rt3-gmx-family-v1".to_string()
}
[0x001c0000, 0x86a00100, 0x00000001, 0xa0000000, ..] => {
evidence.push("leading word 0x001c0000".to_string());
evidence.push("anchor word 0x86a00100".to_string());
evidence.push("third/fourth words 0x00000001 and 0xa0000000".to_string());
"rt3-105-gms-family-v1".to_string()
}
[0x00190000, 0x86a00100, 0x00000001, 0xa0000000, ..] => {
evidence.push("leading word 0x00190000".to_string());
evidence.push("anchor word 0x86a00100".to_string());
evidence.push("third/fourth words 0x00000001 and 0xa0000000".to_string());
"rt3-105-gmx-family-v1".to_string()
}
[0x00130000, 0x86a00100, 0x21000001, 0xa0000100, ..] => {
evidence.push("leading word 0x00130000".to_string());
evidence.push("anchor word 0x86a00100".to_string());
evidence.push("third/fourth words 0x21000001 and 0xa0000100".to_string());
"rt3-105-gms-scenario-family-v1".to_string()
}
[0x00010000, 0x49f00100, 0x00000002, 0xa0000000, ..] => {
evidence.push("leading word 0x00010000".to_string());
evidence.push("anchor word 0x49f00100".to_string());
evidence.push("third/fourth words 0x00000002 and 0xa0000000".to_string());
"rt3-105-gms-alt-family-v1".to_string()
}
[0x86a00100, 0x00000001, 0xa0000000, 0x00000186, ..] => {
evidence.push("window starts directly on 0x86a00100".to_string());
evidence.push("likely same family with missing leading unaligned word".to_string());
"rt3-family-unaligned-anchor".to_string()
}
_ => {
evidence.push(format!(
"unrecognized leading words: {}",
words
.iter()
.take(4)
.map(|word| format!("0x{word:08x}"))
.collect::<Vec<_>>()
.join(", ")
));
"unknown".to_string()
}
};
Some(SmpSecondaryVariantProbe {
aligned_window_offset,
hex_words: words.iter().map(|word| format!("0x{word:08x}")).collect(),
words,
variant_family,
variant_evidence: evidence,
})
}
fn classify_container_profile(
file_extension_hint: Option<&str>,
header_variant_probe: Option<&SmpHeaderVariantProbe>,
secondary_variant_probe: Option<&SmpSecondaryVariantProbe>,
) -> Option<SmpContainerProfile> {
let header_family = header_variant_probe.map(|probe| probe.variant_family.as_str())?;
let secondary_family = secondary_variant_probe.map(|probe| probe.variant_family.as_str())?;
let extension = file_extension_hint.unwrap_or("");
let (profile_family, profile_evidence, is_known_profile) =
match (extension, header_family, secondary_family) {
("gms", "rt3-classic-gms-header-v1", "rt3-gms-family-v1") => (
"rt3-classic-save-container-v1".to_string(),
vec![
"extension .gms".to_string(),
"classic save header family".to_string(),
"classic save secondary window family".to_string(),
],
true,
),
("gmx", "rt3-classic-gmx-header-v1", "rt3-gmx-family-v1") => (
"rt3-classic-sandbox-container-v1".to_string(),
vec![
"extension .gmx".to_string(),
"classic sandbox header family".to_string(),
"classic sandbox secondary window family".to_string(),
],
true,
),
("gms", "rt3-105-common-header-v1", "rt3-105-gms-family-v1") => (
"rt3-105-save-container-v1".to_string(),
vec![
"extension .gms".to_string(),
"1.05 common header family".to_string(),
"1.05 save secondary window family".to_string(),
],
true,
),
("gms", "rt3-105-scenario-save-header-v1", "rt3-105-gms-scenario-family-v1") => (
"rt3-105-scenario-save-container-v1".to_string(),
vec![
"extension .gms".to_string(),
"1.05 scenario-save header family".to_string(),
"1.05 scenario-save secondary window family".to_string(),
],
true,
),
("gms", "rt3-105-alt-save-header-v1", "rt3-105-gms-alt-family-v1") => (
"rt3-105-alt-save-container-v1".to_string(),
vec![
"extension .gms".to_string(),
"1.05 alternate-save header family".to_string(),
"1.05 alternate-save secondary window family".to_string(),
],
true,
),
("gmx", "rt3-105-gmx-header-v1", "rt3-105-gmx-family-v1") => (
"rt3-105-sandbox-container-v1".to_string(),
vec![
"extension .gmx".to_string(),
"1.05 sandbox header family".to_string(),
"1.05 sandbox secondary window family".to_string(),
],
true,
),
("gmp", "rt3-105-common-header-v1", "rt3-family-unaligned-anchor") => (
"rt3-105-map-container-v1".to_string(),
vec![
"extension .gmp".to_string(),
"1.05 common header family".to_string(),
"map-style secondary unaligned anchor".to_string(),
],
true,
),
("gmp", "rt3-105-scenario-save-header-v1", "unknown") => (
"rt3-105-scenario-map-container-v1".to_string(),
vec![
"extension .gmp".to_string(),
"1.05 scenario-map header family".to_string(),
"fixed candidate-availability table range present despite unknown early secondary window".to_string(),
],
true,
),
("gmp", "rt3-105-alt-save-header-v1", "unknown") => (
"rt3-105-alt-map-container-v1".to_string(),
vec![
"extension .gmp".to_string(),
"1.05 alternate-map header family".to_string(),
"fixed candidate-availability table range present despite unknown early secondary window".to_string(),
],
true,
),
("gmp", "rt3-map-header-family", "rt3-family-unaligned-anchor") => (
"rt3-map-container-family".to_string(),
vec![
"extension .gmp".to_string(),
"map header family".to_string(),
"map-style secondary unaligned anchor".to_string(),
],
true,
),
(_, header_family, secondary_family) => (
"unknown".to_string(),
vec![
format!(
"extension {}",
if extension.is_empty() {
"<none>"
} else {
extension
}
),
format!("header family {header_family}"),
format!("secondary family {secondary_family}"),
],
false,
),
};
Some(SmpContainerProfile {
profile_family,
profile_evidence,
is_known_profile,
})
}
fn parse_save_bootstrap_block(
container_profile: Option<&SmpContainerProfile>,
secondary_variant_probe: Option<&SmpSecondaryVariantProbe>,
) -> Option<SmpSaveBootstrapBlock> {
let profile = container_profile?;
let secondary = secondary_variant_probe?;
let words = &secondary.words;
if words.len() < 8 {
return None;
}
let supported = matches!(
profile.profile_family.as_str(),
"rt3-classic-save-container-v1"
| "rt3-105-save-container-v1"
| "rt3-105-scenario-save-container-v1"
| "rt3-105-alt-save-container-v1"
);
if !supported {
return None;
}
Some(SmpSaveBootstrapBlock {
profile_family: profile.profile_family.clone(),
aligned_window_offset: secondary.aligned_window_offset,
leading_word: words[0],
leading_word_hex: format!("0x{:08x}", words[0]),
anchor_word: words[1],
anchor_word_hex: format!("0x{:08x}", words[1]),
descriptor_word_2: words[2],
descriptor_word_2_hex: format!("0x{:08x}", words[2]),
descriptor_word_3: words[3],
descriptor_word_3_hex: format!("0x{:08x}", words[3]),
descriptor_word_4: words[4],
descriptor_word_4_hex: format!("0x{:08x}", words[4]),
descriptor_word_5: words[5],
descriptor_word_5_hex: format!("0x{:08x}", words[5]),
descriptor_word_6: words[6],
descriptor_word_6_hex: format!("0x{:08x}", words[6]),
descriptor_word_7: words[7],
descriptor_word_7_hex: format!("0x{:08x}", words[7]),
})
}
fn parse_runtime_anchor_cycle_block(
bytes: &[u8],
container_profile: Option<&SmpContainerProfile>,
secondary_variant_probe: Option<&SmpSecondaryVariantProbe>,
) -> Option<SmpRuntimeAnchorCycleBlock> {
let profile = container_profile?;
let secondary = secondary_variant_probe?;
let supported = matches!(
profile.profile_family.as_str(),
"rt3-classic-save-container-v1"
| "rt3-classic-sandbox-container-v1"
| "rt3-105-save-container-v1"
| "rt3-105-scenario-save-container-v1"
| "rt3-105-alt-save-container-v1"
| "rt3-105-sandbox-container-v1"
);
if !supported {
return None;
}
let cycle_start_offset = secondary.aligned_window_offset + 0x1c;
let cycle_words = read_u32_window(bytes, cycle_start_offset, 9);
if cycle_words.len() < 9 {
return None;
}
let mut full_cycle_count = 0usize;
let mut cursor = cycle_start_offset;
while read_u32_window(bytes, cursor, cycle_words.len()) == cycle_words {
full_cycle_count += 1;
cursor += cycle_words.len() * 4;
}
if full_cycle_count == 0 {
return None;
}
let mut partial_cycle_word_count = 0usize;
while partial_cycle_word_count < cycle_words.len() {
let offset = cursor + partial_cycle_word_count * 4;
if read_u32_at(bytes, offset) == Some(cycle_words[partial_cycle_word_count]) {
partial_cycle_word_count += 1;
} else {
break;
}
}
let trailer_offset = cursor + partial_cycle_word_count * 4;
let trailer_words = read_u32_window(bytes, trailer_offset, 16);
Some(SmpRuntimeAnchorCycleBlock {
profile_family: profile.profile_family.clone(),
cycle_start_offset,
cycle_hex_words: cycle_words
.iter()
.map(|word| format!("0x{word:08x}"))
.collect(),
cycle_words,
full_cycle_count,
partial_cycle_word_count,
trailer_offset,
trailer_hex_words: trailer_words
.iter()
.map(|word| format!("0x{word:08x}"))
.collect(),
trailer_words,
})
}
fn parse_save_anchor_run_block(
bytes: &[u8],
container_profile: Option<&SmpContainerProfile>,
save_bootstrap_block: Option<&SmpSaveBootstrapBlock>,
) -> Option<SmpSaveAnchorRunBlock> {
let profile = container_profile?;
let bootstrap = save_bootstrap_block?;
let supported = matches!(
profile.profile_family.as_str(),
"rt3-classic-save-container-v1"
| "rt3-105-save-container-v1"
| "rt3-105-scenario-save-container-v1"
| "rt3-105-alt-save-container-v1"
);
if !supported {
return None;
}
let cycle_start_offset = bootstrap.aligned_window_offset + 0x1c;
let cycle_words = read_u32_window(bytes, cycle_start_offset, 9);
if cycle_words.len() < 9 {
return None;
}
let mut full_cycle_count = 0usize;
let mut cursor = cycle_start_offset;
while read_u32_window(bytes, cursor, cycle_words.len()) == cycle_words {
full_cycle_count += 1;
cursor += cycle_words.len() * 4;
}
if full_cycle_count == 0 {
return None;
}
let mut partial_cycle_word_count = 0usize;
while partial_cycle_word_count < cycle_words.len() {
let offset = cursor + partial_cycle_word_count * 4;
if read_u32_at(bytes, offset) == Some(cycle_words[partial_cycle_word_count]) {
partial_cycle_word_count += 1;
} else {
break;
}
}
let trailer_offset = cursor + partial_cycle_word_count * 4;
let trailer_words = read_u32_window(bytes, trailer_offset, 12);
Some(SmpSaveAnchorRunBlock {
profile_family: profile.profile_family.clone(),
cycle_start_offset,
cycle_hex_words: cycle_words
.iter()
.map(|word| format!("0x{word:08x}"))
.collect(),
cycle_words,
full_cycle_count,
partial_cycle_word_count,
trailer_offset,
trailer_hex_words: trailer_words
.iter()
.map(|word| format!("0x{word:08x}"))
.collect(),
trailer_words,
})
}
fn parse_runtime_trailer_block(
container_profile: Option<&SmpContainerProfile>,
runtime_anchor_cycle_block: Option<&SmpRuntimeAnchorCycleBlock>,
) -> Option<SmpRuntimeTrailerBlock> {
let profile = container_profile?;
let anchor = runtime_anchor_cycle_block?;
let words = &anchor.trailer_words;
if words.len() < 16 {
return None;
}
let trailer_family = match profile.profile_family.as_str() {
"rt3-classic-save-container-v1"
if words[..6]
== [
0x00020000, 0x00030000, 0x00010000, 0x00010000, 0x00010000, 0x00020000,
] =>
{
"rt3-classic-save-trailer-v1"
}
"rt3-classic-sandbox-container-v1"
if words[..6]
== [
0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00000000, 0x00000000,
] =>
{
"rt3-classic-sandbox-trailer-v1"
}
"rt3-105-save-container-v1"
| "rt3-105-scenario-save-container-v1"
| "rt3-105-alt-save-container-v1"
if words[..6]
== [
0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00000000, 0x00000000,
] =>
{
"rt3-105-save-trailer-v1"
}
"rt3-105-sandbox-container-v1"
if words[..6]
== [
0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00000000, 0x00000000,
] =>
{
"rt3-105-sandbox-trailer-v1"
}
_ => "unknown",
}
.to_string();
let tag_chunk_id_u16 = (words[6] >> 16) as u16;
let length_high_u16 = (words[7] >> 16) as u16;
let selector_high_u16 = (words[8] >> 16) as u16;
let descriptor_high_u16 = (words[10] >> 16) as u16;
let tag_chunk_id_grounded_alignment =
classify_runtime_trailer_chunk_id_grounded_alignment(tag_chunk_id_u16).map(str::to_string);
let mut trailer_evidence = vec![
format!("container profile {}", profile.profile_family),
format!(
"prefix words {}",
words[..6]
.iter()
.map(|word| format!("0x{word:08x}"))
.collect::<Vec<_>>()
.join(", ")
),
format!("high-16 chunk id 0x{tag_chunk_id_u16:04x} from trailer word 6"),
format!("high-16 span 0x{length_high_u16:04x} from trailer word 7"),
format!("high-16 selector 0x{selector_high_u16:04x} from trailer word 8"),
format!("high-16 descriptor 0x{descriptor_high_u16:04x} from trailer word 10"),
];
if let Some(alignment) = &tag_chunk_id_grounded_alignment {
trailer_evidence.push(alignment.clone());
}
Some(SmpRuntimeTrailerBlock {
profile_family: profile.profile_family.clone(),
trailer_family,
trailer_evidence,
trailer_offset: anchor.trailer_offset,
prefix_words_0_to_5: words[..6].to_vec(),
prefix_hex_words_0_to_5: words[..6]
.iter()
.map(|word| format!("0x{word:08x}"))
.collect(),
tag_word_6: words[6],
tag_word_6_hex: format!("0x{:08x}", words[6]),
tag_chunk_id_u16,
tag_chunk_id_hex: format!("0x{tag_chunk_id_u16:04x}"),
tag_chunk_id_grounded_alignment,
length_word_7: words[7],
length_word_7_hex: format!("0x{:08x}", words[7]),
length_high_u16,
length_high_hex: format!("0x{length_high_u16:04x}"),
selector_word_8: words[8],
selector_word_8_hex: format!("0x{:08x}", words[8]),
selector_high_u16,
selector_high_hex: format!("0x{selector_high_u16:04x}"),
layout_word_9: words[9],
layout_word_9_hex: format!("0x{:08x}", words[9]),
descriptor_word_10: words[10],
descriptor_word_10_hex: format!("0x{:08x}", words[10]),
descriptor_high_u16,
descriptor_high_hex: format!("0x{descriptor_high_u16:04x}"),
descriptor_word_11: words[11],
descriptor_word_11_hex: format!("0x{:08x}", words[11]),
counter_word_12: words[12],
counter_word_12_hex: format!("0x{:08x}", words[12]),
offset_word_13: words[13],
offset_word_13_hex: format!("0x{:08x}", words[13]),
span_word_14: words[14],
span_word_14_hex: format!("0x{:08x}", words[14]),
mode_word_15: words[15],
mode_word_15_hex: format!("0x{:08x}", words[15]),
words: words.to_vec(),
hex_words: words.iter().map(|word| format!("0x{word:08x}")).collect(),
})
}
fn classify_runtime_trailer_chunk_id_grounded_alignment(
tag_chunk_id_u16: u16,
) -> Option<&'static str> {
match tag_chunk_id_u16 {
0x2ee1 => Some(
"High-16 chunk id 0x2ee1 matches the disassembly-grounded map-style bundle family already read by shell_setup_load_selected_profile_bundle_into_payload_record.",
),
_ => None,
}
}
fn parse_runtime_post_span_probe(
bytes: &[u8],
runtime_trailer_block: Option<&SmpRuntimeTrailerBlock>,
) -> Option<SmpRuntimePostSpanProbe> {
let trailer = runtime_trailer_block?;
let span_target_offset = trailer.trailer_offset + trailer.length_high_u16 as usize;
let next_nonzero_offset = find_next_nonzero_offset(bytes, span_target_offset);
let header_candidates =
collect_runtime_post_span_header_candidates(bytes, span_target_offset, 0x8000);
let next_aligned_candidate_offset = header_candidates.first().map(|candidate| candidate.offset);
let next_aligned_candidate_words = header_candidates
.first()
.map(|candidate| candidate.words.clone())
.unwrap_or_default();
let grounded_progress_hits =
find_grounded_progress_high16_hits(bytes, span_target_offset, 0x8000);
Some(SmpRuntimePostSpanProbe {
profile_family: trailer.profile_family.clone(),
span_target_offset,
next_nonzero_offset,
next_aligned_candidate_offset,
next_aligned_candidate_hex_words: next_aligned_candidate_words
.iter()
.map(|word| format!("0x{word:08x}"))
.collect(),
next_aligned_candidate_words,
header_candidates,
grounded_progress_hits,
})
}
fn parse_classic_rehydrate_profile_probe(
bytes: &[u8],
runtime_post_span_probe: Option<&SmpRuntimePostSpanProbe>,
) -> Option<SmpClassicRehydrateProfileProbe> {
let post_span = runtime_post_span_probe?;
if post_span.profile_family != "rt3-classic-save-container-v1" {
return None;
}
let progress_32dc_offset =
parse_grounded_progress_hit_offset(&post_span.grounded_progress_hits, 0x32dc)?;
let progress_3714_offset =
parse_grounded_progress_hit_offset(&post_span.grounded_progress_hits, 0x3714)?;
let progress_3715_offset =
parse_grounded_progress_hit_offset(&post_span.grounded_progress_hits, 0x3715)?;
let packed_profile_offset = progress_3714_offset + 4;
let packed_profile_len = progress_3715_offset.checked_sub(packed_profile_offset)?;
if packed_profile_len != 0x108 {
return None;
}
let ascii_runs =
collect_ascii_previews_in_range(bytes, packed_profile_offset, progress_3715_offset, 4);
let packed_profile_block =
parse_classic_packed_profile_block(bytes, packed_profile_offset, packed_profile_len)?;
Some(SmpClassicRehydrateProfileProbe {
profile_family: post_span.profile_family.clone(),
progress_32dc_offset,
progress_3714_offset,
progress_3715_offset,
packed_profile_offset,
packed_profile_len,
packed_profile_len_hex: format!("0x{packed_profile_len:03x}"),
packed_profile_block,
ascii_runs,
})
}
fn parse_classic_packed_profile_block(
bytes: &[u8],
packed_profile_offset: usize,
packed_profile_len: usize,
) -> Option<SmpClassicPackedProfileBlock> {
let block_end = packed_profile_offset.checked_add(packed_profile_len)?;
if block_end > bytes.len() || packed_profile_len != 0x108 {
return None;
}
let leading_word_0 = read_u32_at(bytes, packed_profile_offset)?;
let trailing_zero_word_count_after_leading_word = (1..4)
.take_while(|index| {
read_u32_at(bytes, packed_profile_offset + (index * 4)).is_some_and(|word| word == 0)
})
.count();
let map_path_offset = 0x13;
let display_name_offset = 0x46;
let stable_nonzero_word_offsets = [0x00usize, 0x10, 0x78, 0x7c, 0x84, 0x88];
let stable_nonzero_words = stable_nonzero_word_offsets
.iter()
.filter_map(|relative_offset| {
let value = read_u32_at(bytes, packed_profile_offset + relative_offset)?;
if value == 0 {
return None;
}
Some(SmpPackedProfileWordLane {
relative_offset: *relative_offset,
relative_offset_hex: format!("0x{relative_offset:02x}"),
value,
value_hex: format!("0x{value:08x}"),
})
})
.collect::<Vec<_>>();
Some(SmpClassicPackedProfileBlock {
relative_len: packed_profile_len,
relative_len_hex: format!("0x{packed_profile_len:03x}"),
leading_word_0,
leading_word_0_hex: format!("0x{leading_word_0:08x}"),
trailing_zero_word_count_after_leading_word,
map_path_offset,
map_path: read_c_string_in_range(bytes, packed_profile_offset + map_path_offset, block_end),
display_name_offset,
display_name: read_c_string_in_range(
bytes,
packed_profile_offset + display_name_offset,
block_end,
),
profile_byte_0x77: bytes[packed_profile_offset + 0x77],
profile_byte_0x77_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0x77]),
profile_byte_0x82: bytes[packed_profile_offset + 0x82],
profile_byte_0x82_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0x82]),
profile_byte_0x97: bytes[packed_profile_offset + 0x97],
profile_byte_0x97_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0x97]),
profile_byte_0xc5: bytes[packed_profile_offset + 0xc5],
profile_byte_0xc5_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0xc5]),
stable_nonzero_words,
})
}
fn parse_rt3_105_packed_profile_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
header_variant_probe: Option<&SmpHeaderVariantProbe>,
container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpRt3105PackedProfileProbe> {
let profile_family = if container_profile.is_some_and(|profile| {
matches!(
profile.profile_family.as_str(),
"rt3-105-save-container-v1"
| "rt3-105-scenario-save-container-v1"
| "rt3-105-alt-save-container-v1"
)
}) {
container_profile
.expect("checked above")
.profile_family
.clone()
} else if file_extension_hint == Some("gms")
&& header_variant_probe.is_some_and(|probe| {
matches!(
probe.variant_family.as_str(),
"rt3-105-common-header-v1"
| "rt3-105-scenario-save-header-v1"
| "rt3-105-alt-save-header-v1"
| "rt3-map-header-family"
)
})
{
"rt3-105-save-analog-block-inferred".to_string()
} else {
return None;
};
if file_extension_hint != Some("gms") {
return None;
}
let map_path_offset = find_c_string_with_suffix_in_range(bytes, 0x7000, 0x9000, ".gmp")?;
let packed_profile_offset = map_path_offset.checked_sub(0x10)?;
let packed_profile_len = 0x108usize;
let block_end = packed_profile_offset.checked_add(packed_profile_len)?;
if block_end > bytes.len() {
return None;
}
let packed_profile_block =
parse_rt3_105_packed_profile_block(bytes, packed_profile_offset, packed_profile_len)?;
let ascii_runs = collect_ascii_previews_in_range(bytes, packed_profile_offset, block_end, 4);
Some(SmpRt3105PackedProfileProbe {
profile_family,
packed_profile_offset,
packed_profile_len,
packed_profile_len_hex: format!("0x{packed_profile_len:03x}"),
packed_profile_block,
ascii_runs,
})
}
fn parse_rt3_105_post_span_bridge_probe(
runtime_trailer_block: Option<&SmpRuntimeTrailerBlock>,
runtime_post_span_probe: Option<&SmpRuntimePostSpanProbe>,
rt3_105_packed_profile_probe: Option<&SmpRt3105PackedProfileProbe>,
) -> Option<SmpRt3105PostSpanBridgeProbe> {
let trailer = runtime_trailer_block?;
let post_span = runtime_post_span_probe?;
let packed_profile = rt3_105_packed_profile_probe?;
let supported = matches!(
trailer.profile_family.as_str(),
"rt3-105-save-container-v1"
| "rt3-105-scenario-save-container-v1"
| "rt3-105-alt-save-container-v1"
| "rt3-105-save-analog-block-inferred"
);
if !supported || trailer.profile_family != post_span.profile_family {
return None;
}
let next_candidate_high_u16_words = post_span
.header_candidates
.first()
.map(|candidate| candidate.high_u16_words.clone())
.unwrap_or_default();
let next_candidate_high_hex_words = next_candidate_high_u16_words
.iter()
.map(|word| format!("0x{word:04x}"))
.collect::<Vec<_>>();
let next_candidate_offset = post_span.next_aligned_candidate_offset;
let next_candidate_delta_from_span_target =
next_candidate_offset.and_then(|offset| offset.checked_sub(post_span.span_target_offset));
let packed_profile_delta_from_span_target = packed_profile
.packed_profile_offset
.checked_sub(post_span.span_target_offset)?;
let next_candidate_delta_from_packed_profile = next_candidate_offset
.map(|offset| offset as i64 - packed_profile.packed_profile_offset as i64);
let mut bridge_evidence = vec![
format!("profile family {}", trailer.profile_family),
format!("selector high {}", trailer.selector_high_hex),
format!("descriptor high {}", trailer.descriptor_high_hex),
format!(
"packed profile sits +0x{packed_profile_delta_from_span_target:x} from span target"
),
];
if let Some(delta) = next_candidate_delta_from_span_target {
bridge_evidence.push(format!("next candidate sits +0x{delta:x} from span target"));
}
if let Some(delta) = next_candidate_delta_from_packed_profile {
bridge_evidence.push(format!(
"next candidate is {delta:+#x} relative to packed profile"
));
}
let bridge_family = match (
trailer.selector_high_u16,
trailer.descriptor_high_u16,
next_candidate_high_u16_words.as_slice(),
) {
(0x7110, 0x7801 | 0x7401, [0x6200, 0x0000, 0xfff7, 0x5515, ..]) => {
bridge_evidence.push(format!(
"selector/descriptor pair 0x7110 -> 0x{:04x}",
trailer.descriptor_high_u16
));
bridge_evidence.push(
"next candidate begins with high-16 lanes 0x6200/0x0000/0xfff7/0x5515"
.to_string(),
);
"rt3-105-save-post-span-bridge-v1"
}
(0x54cd, 0x5901, [0x1500, 0x0100, 0x4100, 0x0200, ..]) => {
bridge_evidence.push("selector/descriptor pair 0x54cd -> 0x5901".to_string());
bridge_evidence.push(
"next candidate begins with high-16 lanes 0x1500/0x0100/0x4100/0x0200"
.to_string(),
);
"rt3-105-alt-save-post-span-bridge-v1"
}
(0x0001, 0x0186, [0x0186, 0x0006, 0x0006, 0x0001, ..]) => {
bridge_evidence.push("selector/descriptor pair 0x0001 -> 0x0186".to_string());
bridge_evidence.push(
"next candidate remains in the local cycle neighborhood with 0x0186/0x0006/0x0006/0x0001"
.to_string(),
);
"rt3-105-scenario-post-span-bridge-v1"
}
_ => "unknown",
}
.to_string();
Some(SmpRt3105PostSpanBridgeProbe {
profile_family: trailer.profile_family.clone(),
bridge_family,
bridge_evidence,
span_target_offset: post_span.span_target_offset,
next_candidate_offset,
next_candidate_delta_from_span_target,
packed_profile_offset: packed_profile.packed_profile_offset,
packed_profile_delta_from_span_target,
next_candidate_delta_from_packed_profile,
selector_high_u16: trailer.selector_high_u16,
selector_high_hex: trailer.selector_high_hex.clone(),
descriptor_high_u16: trailer.descriptor_high_u16,
descriptor_high_hex: trailer.descriptor_high_hex.clone(),
next_candidate_high_u16_words,
next_candidate_high_hex_words,
})
}
fn parse_rt3_105_save_bridge_payload_probe(
bytes: &[u8],
bridge_probe: Option<&SmpRt3105PostSpanBridgeProbe>,
) -> Option<SmpRt3105SaveBridgePayloadProbe> {
let bridge = bridge_probe?;
if bridge.bridge_family != "rt3-105-save-post-span-bridge-v1" {
return None;
}
let primary_block_offset = bridge.next_candidate_offset?;
let primary_block_word_count = 8usize;
let primary_words = read_u32_window(bytes, primary_block_offset, primary_block_word_count);
if primary_words.len() < primary_block_word_count {
return None;
}
let secondary_block_delta_from_primary = 0x1808usize;
let secondary_block_offset = primary_block_offset + secondary_block_delta_from_primary;
let secondary_block_end_offset = bridge.packed_profile_offset;
let secondary_block_len = secondary_block_end_offset.checked_sub(secondary_block_offset)?;
let secondary_preview_word_count = 32usize;
let secondary_words =
read_u32_window(bytes, secondary_block_offset, secondary_preview_word_count);
if secondary_words.len() < secondary_preview_word_count {
return None;
}
let primary_signature_matches = primary_words
== [
0x62000000, 0x00000000, 0xfff70000, 0x55150000, 0x55550000, 0x00000000, 0xfff70000,
0x54550000,
];
let secondary_prefix_matches = secondary_words.starts_with(&[
0x00050000, 0x00050005, 0xfff70000, 0x54540000, 0x545400f9, 0x00f900f9, 0x00f94008,
0x00001555,
]);
let mut evidence = vec![
"bridge family rt3-105-save-post-span-bridge-v1".to_string(),
format!("primary block offset 0x{primary_block_offset:08x}"),
format!("secondary block offset 0x{secondary_block_offset:08x}"),
format!("secondary block delta from primary 0x{secondary_block_delta_from_primary:x}"),
format!("secondary block end offset 0x{secondary_block_end_offset:08x}"),
format!("secondary block span 0x{secondary_block_len:x} bytes"),
];
if primary_signature_matches {
evidence.push(
"primary 8-word bridge block matches the observed 0x6200/0xfff7/0x5515/0x5555 spine"
.to_string(),
);
}
if secondary_prefix_matches {
evidence.push(
"secondary preview matches the observed 0x0005/0xfff7/0x5454 dense block prefix"
.to_string(),
);
}
Some(SmpRt3105SaveBridgePayloadProbe {
profile_family: bridge.profile_family.clone(),
bridge_family: bridge.bridge_family.clone(),
primary_block_offset,
primary_block_len: primary_block_word_count * 4,
primary_block_len_hex: format!("0x{:02x}", primary_block_word_count * 4),
primary_hex_words: primary_words
.iter()
.map(|word| format!("0x{word:08x}"))
.collect(),
primary_words,
secondary_block_offset,
secondary_block_delta_from_primary,
secondary_block_delta_from_primary_hex: format!("0x{secondary_block_delta_from_primary:x}"),
secondary_block_end_offset,
secondary_block_len,
secondary_block_len_hex: format!("0x{secondary_block_len:x}"),
secondary_preview_word_count,
secondary_hex_words: secondary_words
.iter()
.map(|word| format!("0x{word:08x}"))
.collect(),
secondary_words,
evidence,
})
}
fn parse_save_world_selection_context_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpSaveWorldSelectionContextProbe> {
if file_extension_hint != Some("gms") {
return None;
}
let profile = container_profile?;
let supported = matches!(
profile.profile_family.as_str(),
"rt3-classic-save-container-v1"
| "rt3-105-save-container-v1"
| "rt3-105-scenario-save-container-v1"
| "rt3-105-alt-save-container-v1"
);
if !supported {
return None;
}
for chunk_tag_offset in find_u32_le_offsets(bytes, RT3_SAVE_WORLD_BLOCK_CHUNK_TAG) {
let payload_offset = chunk_tag_offset + 4;
let next_chunk_tag_offset = payload_offset.checked_add(RT3_SAVE_WORLD_BLOCK_LEN)?;
if read_u32_at(bytes, next_chunk_tag_offset) != Some(RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG) {
continue;
}
let selected_company_id_offset =
payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET;
let selected_chairman_profile_id_offset =
payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET;
let chairman_slot_selector_offset =
payload_offset + RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_SELECTOR_RELATIVE_OFFSET;
let campaign_override_flag_offset =
payload_offset + RT3_SAVE_WORLD_BLOCK_CAMPAIGN_OVERRIDE_FLAG_RELATIVE_OFFSET;
let chairman_role_gate_offset =
payload_offset + RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_RELATIVE_OFFSET;
let selected_company_id = read_u32_at(bytes, selected_company_id_offset)?;
let selected_chairman_profile_id = read_u32_at(bytes, selected_chairman_profile_id_offset)?;
let chairman_slot_selectors = bytes
.get(
chairman_slot_selector_offset
..chairman_slot_selector_offset + RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_COUNT,
)?
.to_vec();
let campaign_override_flag = *bytes.get(campaign_override_flag_offset)?;
let chairman_role_gate_bytes = (0..RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_COUNT)
.map(|slot_index| {
bytes
.get(
chairman_role_gate_offset
+ slot_index * RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_STRIDE,
)
.copied()
})
.collect::<Option<Vec<_>>>()?;
return Some(SmpSaveWorldSelectionContextProbe {
profile_family: profile.profile_family.clone(),
source_kind: "save-direct-world-block".to_string(),
semantic_family: "scenario-selected-company-and-chairman-context".to_string(),
chunk_tag_offset,
payload_offset,
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
selected_company_id_offset,
selected_company_id,
selected_company_id_hex: format!("0x{selected_company_id:08x}"),
selected_chairman_profile_id_offset,
selected_chairman_profile_id,
selected_chairman_profile_id_hex: format!("0x{selected_chairman_profile_id:08x}"),
chairman_slot_selector_offset,
chairman_slot_selectors,
campaign_override_flag_offset,
campaign_override_flag,
campaign_override_flag_hex: format!("0x{campaign_override_flag:02x}"),
chairman_role_gate_offset,
chairman_role_gate_bytes,
evidence: vec![
format!(
"chunk tag 0x32c8 at 0x{chunk_tag_offset:x} matches the fixed [world+0x04] save block"
),
format!(
"next chunk tag 0x32c9 appears at 0x{next_chunk_tag_offset:x}, matching the documented 0x4f2c payload span"
),
format!(
"selected company id comes from payload +0x{:x} ([world+0x21])",
RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET
),
format!(
"selected chairman profile id comes from payload +0x{:x} ([world+0x25])",
RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET
),
format!(
"16 chairman slot selector bytes come from payload +0x{:x} ([world+0x87])",
RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_SELECTOR_RELATIVE_OFFSET
),
format!(
"campaign override flag comes from payload +0x{:x} ([world+0xc5])",
RT3_SAVE_WORLD_BLOCK_CAMPAIGN_OVERRIDE_FLAG_RELATIVE_OFFSET
),
format!(
"chairman role-gate bytes come from payload +0x{:x} + slot*0x{:x} ([world+0x0bc3+slot*9])",
RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_RELATIVE_OFFSET,
RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_STRIDE
),
],
});
}
None
}
fn parse_save_world_issue_37_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpSaveWorldIssue37Probe> {
if file_extension_hint != Some("gms") {
return None;
}
let profile = container_profile?;
let supported = matches!(
profile.profile_family.as_str(),
"rt3-classic-save-container-v1"
| "rt3-105-save-container-v1"
| "rt3-105-scenario-save-container-v1"
| "rt3-105-alt-save-container-v1"
);
if !supported {
return None;
}
for chunk_tag_offset in find_u32_le_offsets(bytes, RT3_SAVE_WORLD_BLOCK_CHUNK_TAG) {
let payload_offset = chunk_tag_offset + 4;
let next_chunk_tag_offset = payload_offset.checked_add(RT3_SAVE_WORLD_BLOCK_LEN)?;
if read_u32_at(bytes, next_chunk_tag_offset) != Some(RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG) {
continue;
}
let issue_value_lane = build_save_dword_candidate(
bytes,
payload_offset,
"issue_0x37_value",
RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET,
)?;
let issue_37_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET,
)?;
let issue_38_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET + 1,
)?;
let issue_39_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET + 2,
)?;
let issue_3a_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET + 3,
)?;
let multiplier_lane = build_save_dword_candidate(
bytes,
payload_offset,
"issue_0x37_multiplier",
RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_MULTIPLIER_RELATIVE_OFFSET,
)?;
let issue_opinion_base_terms_raw_i32 = build_save_i32_term_strip(
bytes,
payload_offset,
RT3_SAVE_WORLD_BLOCK_ISSUE_OPINION_BASE_TERMS_OFFSET,
RT3_SAVE_WORLD_BLOCK_ISSUE_OPINION_TERM_COUNT,
)?;
return Some(SmpSaveWorldIssue37Probe {
profile_family: profile.profile_family.clone(),
source_kind: "save-direct-world-block".to_string(),
semantic_family: "scenario-save-world-issue-0x37".to_string(),
chunk_tag_offset,
payload_offset,
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
issue_37_raw_u8,
issue_37_raw_hex: format!("0x{issue_37_raw_u8:02x}"),
issue_38_raw_u8,
issue_38_raw_hex: format!("0x{issue_38_raw_u8:02x}"),
issue_39_raw_u8,
issue_39_raw_hex: format!("0x{issue_39_raw_u8:02x}"),
issue_3a_raw_u8,
issue_3a_raw_hex: format!("0x{issue_3a_raw_u8:02x}"),
issue_value_lane,
multiplier_lane,
issue_opinion_base_terms_raw_i32,
evidence: vec![
format!(
"chunk tag 0x32c8 at 0x{chunk_tag_offset:x} matches the fixed [world+0x04] save block"
),
format!(
"next chunk tag 0x32c9 appears at 0x{next_chunk_tag_offset:x}, matching the documented 0x4f2c payload span"
),
format!(
"issue value lane uses payload +0x{:x} ([world+0x2d]); atlas notes tie 0x004339b0 to the clamped 0..4 issue-0x37 setter there",
RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET
),
format!(
"multiplier lane uses payload +0x{:x} ([world+0x29]); atlas notes tie 0x004339b0 to one companion scalar at that lane before company share-price refresh",
RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_MULTIPLIER_RELATIVE_OFFSET
),
format!(
"the adjacent byte strip at payload +0x{:x}..+0x{:x} carries raw issue slots 0x37..0x3a as {:02x} {:02x} {:02x} {:02x}",
RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET,
RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET + 3,
issue_37_raw_u8,
issue_38_raw_u8,
issue_39_raw_u8,
issue_3a_raw_u8
),
],
});
}
None
}
fn parse_save_world_finance_neighborhood_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpSaveWorldFinanceNeighborhoodProbe> {
if file_extension_hint != Some("gms") {
return None;
}
let profile = container_profile?;
let supported = matches!(
profile.profile_family.as_str(),
"rt3-classic-save-container-v1"
| "rt3-105-save-container-v1"
| "rt3-105-scenario-save-container-v1"
| "rt3-105-alt-save-container-v1"
);
if !supported {
return None;
}
for chunk_tag_offset in find_u32_le_offsets(bytes, RT3_SAVE_WORLD_BLOCK_CHUNK_TAG) {
let payload_offset = chunk_tag_offset + 4;
let next_chunk_tag_offset = payload_offset.checked_add(RT3_SAVE_WORLD_BLOCK_LEN)?;
if read_u32_at(bytes, next_chunk_tag_offset) != Some(RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG) {
continue;
}
let current_calendar_tuple_word_lane = build_save_dword_candidate(
bytes,
payload_offset,
"current_calendar_tuple_word",
RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET,
)?;
let packed_year_word_raw_u16 = read_u16_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET,
)?;
let partial_year_progress_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET + 2,
)?;
let current_calendar_tuple_word_2_lane = build_save_dword_candidate(
bytes,
payload_offset,
"current_calendar_tuple_word_2",
RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_2_RELATIVE_OFFSET,
)?;
let absolute_counter_lane = build_save_dword_candidate(
bytes,
payload_offset,
"absolute_calendar_counter",
RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_RELATIVE_OFFSET,
)?;
let absolute_counter_mirror_lane = build_save_dword_candidate(
bytes,
payload_offset,
"absolute_calendar_counter_mirror",
RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_MIRROR_RELATIVE_OFFSET,
)?;
let stock_policy_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_STOCK_POLICY_RELATIVE_OFFSET,
)?;
let bond_policy_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_BOND_POLICY_RELATIVE_OFFSET,
)?;
let bankruptcy_policy_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_BANKRUPTCY_POLICY_RELATIVE_OFFSET,
)?;
let dividend_policy_raw_u8 = read_u8_at(
bytes,
payload_offset + RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET,
)?;
let building_density_growth_setting_lane = build_save_dword_candidate(
bytes,
payload_offset,
"building_density_growth_setting",
RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET,
)?;
let dword_candidates =
build_save_world_finance_neighborhood_candidates(bytes, payload_offset)?;
return Some(SmpSaveWorldFinanceNeighborhoodProbe {
profile_family: profile.profile_family.clone(),
source_kind: "save-direct-world-block".to_string(),
semantic_family: "scenario-save-world-finance-neighborhood".to_string(),
chunk_tag_offset,
payload_offset,
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
packed_year_word_raw_u16,
packed_year_word_raw_hex: format!("0x{packed_year_word_raw_u16:04x}"),
partial_year_progress_raw_u8,
partial_year_progress_raw_hex: format!("0x{partial_year_progress_raw_u8:02x}"),
current_calendar_tuple_word_lane,
current_calendar_tuple_word_2_lane,
absolute_counter_lane,
absolute_counter_mirror_lane,
stock_policy_raw_u8,
stock_policy_raw_hex: format!("0x{stock_policy_raw_u8:02x}"),
bond_policy_raw_u8,
bond_policy_raw_hex: format!("0x{bond_policy_raw_u8:02x}"),
bankruptcy_policy_raw_u8,
bankruptcy_policy_raw_hex: format!("0x{bankruptcy_policy_raw_u8:02x}"),
dividend_policy_raw_u8,
dividend_policy_raw_hex: format!("0x{dividend_policy_raw_u8:02x}"),
building_density_growth_setting_lane,
dword_candidates,
evidence: vec![
format!(
"chunk tag 0x32c8 at 0x{chunk_tag_offset:x} matches the fixed [world+0x04] save block"
),
format!(
"next chunk tag 0x32c9 appears at 0x{next_chunk_tag_offset:x}, matching the documented 0x4f2c payload span"
),
format!(
"payload +0x{:x}/+0x{:x}/+0x{:x} carry the saved world calendar tuple and absolute counter lanes that later company stock-issue cooldown readers compare against",
RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET,
RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_2_RELATIVE_OFFSET,
RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_RELATIVE_OFFSET
),
format!(
"payload +0x{:x}/+0x{:x}/+0x{:x}/+0x{:x} carry the stock, bond, bankruptcy, and dividend finance-policy bytes mirrored from scenario offsets 0x4a87/0x4a8b/0x4a8f/0x4a93",
RT3_SAVE_WORLD_BLOCK_STOCK_POLICY_RELATIVE_OFFSET,
RT3_SAVE_WORLD_BLOCK_BOND_POLICY_RELATIVE_OFFSET,
RT3_SAVE_WORLD_BLOCK_BANKRUPTCY_POLICY_RELATIVE_OFFSET,
RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET
),
format!(
"payload +0x{:x} carries the fixed-world building-density growth setting mirrored from `[world+0x4c7c]`, which the annual repurchase and dividend policy helpers both read directly",
RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET
),
"finance-neighborhood candidates cover the fixed dword strip around the grounded world calendar tuple, absolute-counter, selection-context, and issue-0x37 lanes so broader finance reader closure can build on one rehosted owner surface.".to_string(),
],
});
}
None
}
fn build_save_world_finance_neighborhood_candidates(
bytes: &[u8],
payload_offset: usize,
) -> Option<Vec<SmpSaveDwordCandidate>> {
(0..RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_WINDOW_LEN_DWORDS)
.map(|index| {
let relative_offset =
RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_ROOT_RELATIVE_OFFSET + index * 4;
let label = RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_CANDIDATE_FIELDS
.iter()
.find(|(_, named_offset)| *named_offset == relative_offset)
.map(|(name, _)| (*name).to_string())
.unwrap_or_else(|| format!("finance_neighborhood_word_{:02}", index + 1));
build_save_dword_candidate(bytes, payload_offset, &label, relative_offset)
})
.collect()
}
fn parse_save_world_economic_tuning_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpSaveWorldEconomicTuningProbe> {
if file_extension_hint != Some("gms") {
return None;
}
let profile = container_profile?;
let supported = matches!(
profile.profile_family.as_str(),
"rt3-classic-save-container-v1"
| "rt3-105-save-container-v1"
| "rt3-105-scenario-save-container-v1"
| "rt3-105-alt-save-container-v1"
);
if !supported {
return None;
}
for chunk_tag_offset in find_u32_le_offsets(bytes, RT3_SAVE_WORLD_BLOCK_CHUNK_TAG) {
let payload_offset = chunk_tag_offset + 4;
let next_chunk_tag_offset = payload_offset.checked_add(RT3_SAVE_WORLD_BLOCK_LEN)?;
if read_u32_at(bytes, next_chunk_tag_offset) != Some(RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG) {
continue;
}
let mirror_lane = build_save_dword_candidate(
bytes,
payload_offset,
"economic_tuning_mirror_lane_0",
RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET,
)?;
let tuning_lanes = RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_PRIMARY_RELATIVE_OFFSETS
.iter()
.enumerate()
.map(|(lane_index, relative_offset)| {
build_save_dword_candidate(
bytes,
payload_offset,
&format!("economic_tuning_lane_{lane_index}"),
*relative_offset,
)
})
.collect::<Option<Vec<_>>>()?;
return Some(SmpSaveWorldEconomicTuningProbe {
profile_family: profile.profile_family.clone(),
source_kind: "save-direct-world-block".to_string(),
semantic_family: "scenario-save-world-economic-tuning".to_string(),
chunk_tag_offset,
payload_offset,
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
mirror_lane,
tuning_lanes,
evidence: vec![
format!(
"chunk tag 0x32c8 at 0x{chunk_tag_offset:x} matches the fixed [world+0x04] save block"
),
format!(
"next chunk tag 0x32c9 appears at 0x{next_chunk_tag_offset:x}, matching the documented 0x4f2c payload span"
),
format!(
"mirror lane uses payload +0x{:x} ([world+0x0bde])",
RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET
),
format!(
"primary tuning lanes use payload offsets {} matching the documented [world+0x0be2..+0x0bf6] float block",
RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_PRIMARY_RELATIVE_OFFSETS
.iter()
.map(|offset| format!("0x{offset:x}"))
.collect::<Vec<_>>()
.join(", ")
),
"Current atlas evidence keeps this fixed six-float world tuning band separate from the issue-0x37 investor-confidence lane."
.to_string(),
],
});
}
None
}
fn parse_save_company_collection_header_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
parse_save_tagged_collection_header_probe(
bytes,
file_extension_hint,
container_profile,
0x000061a9,
0x000061aa,
0x000061ab,
"save-company-tagged-header-counts",
"scenario-save-company-header-counts",
|header| {
header.direct_collection_flag == 1
&& header.live_id_bound >= 1
&& header.live_id_bound <= 0x20
&& header.live_record_count <= header.live_id_bound
&& header.direct_record_stride >= 0x1000
},
vec![
"save-side company collection uses tagged header family 0x61a9/0x61aa/0x61ab".to_string(),
"package-save per-company callback is currently grounded as a no-op stub, so this probe only claims header-level collection counts, not per-company payload".to_string(),
],
)
}
fn parse_save_chairman_profile_collection_header_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
parse_save_tagged_collection_header_probe(
bytes,
file_extension_hint,
container_profile,
0x00005209,
0x0000520a,
0x0000520b,
"save-chairman-profile-tagged-header-counts",
"scenario-save-chairman-profile-header-counts",
|header| {
header.direct_collection_flag == 1
&& header.live_id_bound >= 1
&& header.live_id_bound <= 0x20
&& header.live_record_count <= header.live_id_bound
&& header.direct_record_stride >= 0x800
&& header.direct_record_stride <= 0x2000
},
vec![
"save-side chairman/profile collection uses tagged header family 0x5209/0x520a/0x520b".to_string(),
"the direct-record chairman/profile family is the large-stride tagged collection with embedded name and biography payload, not the smaller train-side 0x5209 family".to_string(),
],
)
}
fn parse_save_region_collection_header_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
parse_save_tagged_collection_header_probe(
bytes,
file_extension_hint,
container_profile,
0x00005209,
0x0000520a,
0x0000520b,
"save-region-tagged-header-counts",
"scenario-save-region-header-counts",
|header| {
header.direct_collection_flag == 1
&& header.direct_record_stride >= 0x100
&& header.direct_record_stride <= 0x400
&& header.live_id_bound >= 0x10
&& header.live_id_bound <= 0x100
&& header.live_record_count >= 1
&& header.live_record_count <= header.live_id_bound
},
vec![
"save-side live region collection shares tagged header family 0x5209/0x520a/0x520b with other indexed direct-record bundles".to_string(),
"the grounded region-side candidate is the smaller direct-record family with stride 0x1d5 and live_id_bound/count in the city-region range, not the larger chairman/profile family".to_string(),
],
)
}
fn parse_save_region_collection_directory_probe(
bytes: &[u8],
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
) -> Option<SmpSaveRegionCollectionDirectoryProbe> {
let header_probe = header_probe?;
if header_probe.source_kind != "save-region-tagged-header-counts" {
return None;
}
let metadata_payload =
bytes.get(header_probe.metadata_tag_offset + 4..header_probe.records_tag_offset)?;
let directory_root_byte_offset =
SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX.checked_mul(4)?;
let live_record_count = header_probe.live_record_count as usize;
let directory_len_dwords =
live_record_count.checked_mul(SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT)?;
let directory_len_bytes = directory_len_dwords.checked_mul(4)?;
let directory_bytes = metadata_payload.get(
directory_root_byte_offset..directory_root_byte_offset.checked_add(directory_len_bytes)?,
)?;
let mut entries = Vec::with_capacity(live_record_count);
for index in 0..live_record_count {
let entry_offset =
index.checked_mul(SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT * 4)?;
let payload_relative_offset = read_u32_at(directory_bytes, entry_offset)?;
let previous_live_entry_id = read_u32_at(directory_bytes, entry_offset + 4)?;
let next_live_entry_id = read_u32_at(directory_bytes, entry_offset + 8)?;
entries.push(SmpSaveRegionCollectionDirectoryEntryProbe {
live_entry_id: (index + 1) as u32,
payload_relative_offset,
payload_relative_offset_hex: format!("0x{payload_relative_offset:08x}"),
payload_absolute_offset: header_probe
.metadata_tag_offset
.checked_add(4)?
.checked_add(payload_relative_offset as usize)?,
previous_live_entry_id,
previous_live_entry_id_hex: format!("0x{previous_live_entry_id:08x}"),
next_live_entry_id,
next_live_entry_id_hex: format!("0x{next_live_entry_id:08x}"),
});
}
let chain_head_live_entry_id = entries
.iter()
.find(|entry| entry.previous_live_entry_id == 0)
.map(|entry| entry.live_entry_id);
let chain_tail_live_entry_id = entries
.iter()
.find(|entry| entry.next_live_entry_id == 0)
.map(|entry| entry.live_entry_id);
let monotonic_offsets = entries
.windows(2)
.all(|window| window[0].payload_relative_offset < window[1].payload_relative_offset);
Some(SmpSaveRegionCollectionDirectoryProbe {
profile_family: header_probe.profile_family.clone(),
source_kind: "save-region-live-directory".to_string(),
semantic_family: "scenario-save-region-live-directory".to_string(),
metadata_tag_offset: header_probe.metadata_tag_offset,
records_tag_offset: header_probe.records_tag_offset,
close_tag_offset: header_probe.close_tag_offset,
directory_root_dword_index: SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX,
directory_entry_dword_count: SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT,
live_record_count: header_probe.live_record_count,
live_id_bound: header_probe.live_id_bound,
chain_head_live_entry_id,
chain_tail_live_entry_id,
entries,
evidence: vec![
"save-side region metadata payload exposes a live-entry directory immediately after the first 16 dwords, before the records tag".to_string(),
format!(
"region live directory decodes {} triplets of (payload_relative_offset, prev_live_entry_id, next_live_entry_id) from metadata dword index {}",
header_probe.live_record_count,
SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX
),
format!(
"decoded directory preserves a head/tail chain {:?}->{:?} and monotonic payload offsets={monotonic_offsets}",
chain_head_live_entry_id, chain_tail_live_entry_id
),
],
})
}
fn parse_save_placed_structure_collection_header_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
parse_save_tagged_collection_header_probe(
bytes,
file_extension_hint,
container_profile,
0x000036b1,
0x000036b2,
0x000036b3,
"save-placed-structure-tagged-header-counts",
"scenario-save-placed-structure-header-counts",
|header| {
header.direct_collection_flag == 0
&& header.direct_record_stride >= 1
&& header.direct_record_stride <= 0x20
&& header.live_id_bound >= 0x100
&& header.live_record_count >= 0x100
&& header.live_record_count <= header.live_id_bound
},
vec![
"save-side placed-structure collection uses tagged family 0x36b1/0x36b2/0x36b3 beneath the wider local-runtime and route-entry rebuild owners".to_string(),
"current evidence only grounds header-level placed-structure collection counts here; direct record-body reconstruction still needs the later per-entry load/save slot study.".to_string(),
],
)
}
#[derive(Clone, Copy)]
struct IndexedCollectionHeaderSummary {
metadata_tag_offset: usize,
records_tag_offset: usize,
close_tag_offset: usize,
direct_collection_flag: u32,
direct_record_stride: u32,
live_id_bound: u32,
live_record_count: u32,
header_words: [u32; INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT],
}
fn parse_save_tagged_collection_header_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
metadata_tag: u32,
records_tag: u32,
close_tag: u32,
source_kind: &str,
semantic_family: &str,
predicate: impl Fn(IndexedCollectionHeaderSummary) -> bool,
mut evidence: Vec<String>,
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
if file_extension_hint != Some("gms") {
return None;
}
let profile = container_profile?;
if !matches!(
profile.profile_family.as_str(),
"rt3-classic-save-container-v1"
| "rt3-105-save-container-v1"
| "rt3-105-scenario-save-container-v1"
| "rt3-105-alt-save-container-v1"
) {
return None;
}
let metadata_offsets = find_u32_le_offsets(bytes, metadata_tag);
let records_offsets = find_u32_le_offsets(bytes, records_tag);
let close_offsets = find_u32_le_offsets(bytes, close_tag);
let summary = metadata_offsets
.into_iter()
.filter_map(|metadata_tag_offset| {
let records_tag_offset = records_offsets
.iter()
.copied()
.find(|offset| *offset > metadata_tag_offset)?;
let close_tag_offset = close_offsets
.iter()
.copied()
.find(|offset| *offset > records_tag_offset)?;
let payload = bytes.get(metadata_tag_offset + 4..records_tag_offset)?;
if payload.len() < INDEXED_COLLECTION_SERIALIZED_HEADER_LEN {
return None;
}
let header_words = (0..INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT)
.map(|index| read_u32_at(payload, index * 4))
.collect::<Option<Vec<_>>>()?;
let header_words: [u32; INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT] =
header_words.try_into().ok()?;
let summary = IndexedCollectionHeaderSummary {
metadata_tag_offset,
records_tag_offset,
close_tag_offset,
direct_collection_flag: header_words[0],
direct_record_stride: header_words[1],
live_id_bound: header_words[4],
live_record_count: header_words[5],
header_words,
};
predicate(summary).then_some(summary)
})
.next()?;
evidence.push(format!(
"exact little-endian u32 tag family 0x{metadata_tag:04x}/0x{records_tag:04x}/0x{close_tag:04x} appears at file offsets 0x{:x}/0x{:x}/0x{:x}",
summary.metadata_tag_offset, summary.records_tag_offset, summary.close_tag_offset
));
evidence.push(format!(
"header words report direct_collection_flag={}, direct_record_stride=0x{:x}, live_id_bound={}, live_record_count={}",
summary.direct_collection_flag,
summary.direct_record_stride,
summary.live_id_bound,
summary.live_record_count
));
Some(SmpSaveTaggedCollectionHeaderProbe {
profile_family: profile.profile_family.clone(),
source_kind: source_kind.to_string(),
semantic_family: semantic_family.to_string(),
metadata_tag_offset: summary.metadata_tag_offset,
records_tag_offset: summary.records_tag_offset,
close_tag_offset: summary.close_tag_offset,
direct_collection_flag: summary.direct_collection_flag,
direct_collection_flag_hex: format!("0x{:08x}", summary.direct_collection_flag),
direct_record_stride: summary.direct_record_stride,
direct_record_stride_hex: format!("0x{:08x}", summary.direct_record_stride),
live_id_bound: summary.live_id_bound,
live_id_bound_hex: format!("0x{:08x}", summary.live_id_bound),
live_record_count: summary.live_record_count,
live_record_count_hex: format!("0x{:08x}", summary.live_record_count),
header_words: summary.header_words.to_vec(),
header_hex_words: summary
.header_words
.iter()
.map(|word| format!("0x{word:08x}"))
.collect(),
evidence,
})
}
fn parse_rt3_105_save_name_table_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
bridge_payload_probe: Option<&SmpRt3105SaveBridgePayloadProbe>,
) -> Option<SmpRt3105SaveNameTableProbe> {
let (
profile_family,
source_kind,
header_offset,
entries_offset,
block_end_offset,
mut evidence,
) = if let Some(payload) = bridge_payload_probe {
(
payload.profile_family.clone(),
"save-bridge-secondary-block".to_string(),
payload.secondary_block_offset + 0x354,
payload.secondary_block_offset + 0x3b5,
payload.secondary_block_end_offset,
vec![
"common-save bridge payload branch".to_string(),
format!(
"secondary block span 0x{:x}..0x{:x}",
payload.secondary_block_offset, payload.secondary_block_end_offset
),
],
)
} else {
let profile_family = container_profile
.map(|profile| profile.profile_family.clone())
.unwrap_or_else(|| "unknown".to_string());
let extension = file_extension_hint.unwrap_or("");
let source_kind = match extension {
"gmp" => "map-fixed-catalog-range",
"gms" => "save-fixed-catalog-range",
"gmx" => "sandbox-fixed-catalog-range",
_ => "fixed-catalog-range",
}
.to_string();
(
profile_family,
source_kind,
0x6a70,
0x6ad1,
0x73c0,
vec![
"fixed catalog range branch".to_string(),
"using observed shared 1.05 candidate-availability table offsets".to_string(),
],
)
};
let entry_stride = 0x22usize;
if block_end_offset > bytes.len() {
return None;
}
if !matches_candidate_availability_table_header(bytes, header_offset) {
return None;
}
let observed_entry_capacity = read_u32_at(bytes, header_offset + 0x1c)? as usize;
let observed_entry_count = read_u32_at(bytes, header_offset + 0x20)? as usize;
let entries_len = observed_entry_count.checked_mul(entry_stride)?;
let entries_end_offset = entries_offset.checked_add(entries_len)?;
if observed_entry_count == 0 || observed_entry_capacity < observed_entry_count {
return None;
}
if entries_end_offset > block_end_offset || entries_end_offset > bytes.len() {
return None;
}
let mut entries = Vec::with_capacity(observed_entry_count);
for index in 0..observed_entry_count {
let offset = entries_offset + index * entry_stride;
let chunk = &bytes[offset..offset + entry_stride];
let nul_index = chunk
.iter()
.position(|byte| *byte == 0)
.unwrap_or(entry_stride);
let text = std::str::from_utf8(&chunk[..nul_index]).ok()?.to_string();
let trailer_word = read_u32_at(bytes, offset + entry_stride - 4)?;
entries.push(SmpRt3105SaveNameTableEntry {
index,
offset,
text,
availability_dword: trailer_word,
availability_dword_hex: format!("0x{trailer_word:08x}"),
trailer_word,
trailer_word_hex: format!("0x{trailer_word:08x}"),
});
}
let zero_trailer_entry_names = entries
.iter()
.filter(|entry| entry.trailer_word == 0)
.map(|entry| entry.text.clone())
.collect::<Vec<_>>();
let zero_trailer_entry_count = zero_trailer_entry_names.len();
let nonzero_trailer_entry_count = entries.len().saturating_sub(zero_trailer_entry_count);
let mut distinct_trailer_words = entries
.iter()
.map(|entry| entry.trailer_word)
.collect::<Vec<_>>();
distinct_trailer_words.sort_unstable();
distinct_trailer_words.dedup();
let distinct_trailer_hex_words = distinct_trailer_words
.iter()
.map(|word| format!("0x{word:08x}"))
.collect::<Vec<_>>();
let trailing_footer_hex = hex_encode(&bytes[entries_end_offset..block_end_offset]);
let footer = &bytes[entries_end_offset..block_end_offset];
if footer.len() != 9 {
return None;
}
let footer_progress_word_0 = u32::from_le_bytes([footer[0], footer[1], footer[2], footer[3]]);
let footer_progress_word_1 = u32::from_le_bytes([footer[4], footer[5], footer[6], footer[7]]);
let footer_trailing_byte = footer[8];
let mut footer_grounded_alignments = Vec::new();
for value in [footer_progress_word_0, footer_progress_word_1] {
if let Some(alignment) = classify_name_table_footer_progress_alignment(value) {
footer_grounded_alignments.push(alignment.to_string());
}
}
evidence.extend([
format!("header offset 0x{header_offset:08x}"),
format!("entries offset 0x{entries_offset:08x}"),
format!("entry stride 0x{entry_stride:x}"),
format!("observed entry capacity {}", observed_entry_capacity),
format!("observed entry count {}", observed_entry_count),
format!("zero-trailer entries {}", zero_trailer_entry_count),
format!(
"trailing footer {} bytes after last entry",
block_end_offset - entries_end_offset
),
]);
let semantic_alignment = vec![
"Matches the grounded scenario-side named candidate-availability table shape under 0x00437743.".to_string(),
"Entry layout matches 0x00434ea0/0x00434f20: name slot at +0x00..+0x1d and availability dword at +0x1e.".to_string(),
"The shared map/save range suggests this catalog is bundled in source map content and later mirrored into scenario state [state+0x66b2].".to_string(),
];
Some(SmpRt3105SaveNameTableProbe {
profile_family,
source_kind,
semantic_family: "scenario-named-candidate-availability-table".to_string(),
semantic_alignment,
header_offset,
header_word_0: read_u32_at(bytes, header_offset)?,
header_word_0_hex: format!("0x{:08x}", read_u32_at(bytes, header_offset)?),
header_word_1: read_u32_at(bytes, header_offset + 4)?,
header_word_1_hex: format!("0x{:08x}", read_u32_at(bytes, header_offset + 4)?),
header_word_2: read_u32_at(bytes, header_offset + 8)?,
header_word_2_hex: format!("0x{:08x}", read_u32_at(bytes, header_offset + 8)?),
entry_stride,
entry_stride_hex: format!("0x{entry_stride:x}"),
header_prefix_word_count: 11,
observed_entry_capacity,
observed_entry_count,
zero_trailer_entry_count,
nonzero_trailer_entry_count,
distinct_trailer_words,
distinct_trailer_hex_words,
zero_trailer_entry_names,
entries_offset,
entries_end_offset,
trailing_footer_hex,
footer_progress_word_0,
footer_progress_word_0_hex: format!("0x{footer_progress_word_0:08x}"),
footer_progress_word_1,
footer_progress_word_1_hex: format!("0x{footer_progress_word_1:08x}"),
footer_trailing_byte,
footer_trailing_byte_hex: format!("0x{footer_trailing_byte:02x}"),
footer_grounded_alignments,
entries,
evidence,
})
}
const RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE: usize = 0x41;
const RT3_105_SAVE_NAMED_LOCOMOTIVE_MIN_ENTRY_COUNT: usize = 8;
const RT3_105_SAVE_NAMED_LOCOMOTIVE_MAX_SEARCH_SPAN: usize = 0x4000;
fn parse_rt3_105_save_named_locomotive_availability_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
packed_profile_probe: Option<&SmpRt3105PackedProfileProbe>,
) -> Option<SmpRt3105SaveNamedLocomotiveAvailabilityProbe> {
let packed_profile_probe = packed_profile_probe?;
let extension = file_extension_hint.unwrap_or("");
let profile_family = container_profile
.map(|profile| profile.profile_family.clone())
.unwrap_or_else(|| packed_profile_probe.profile_family.clone());
if !matches!(extension, "gms" | "gmx") || !profile_family.contains("save-container") {
return None;
}
let search_start = packed_profile_probe
.packed_profile_offset
.checked_add(packed_profile_probe.packed_profile_len)?;
let search_end = search_start
.checked_add(RT3_105_SAVE_NAMED_LOCOMOTIVE_MAX_SEARCH_SPAN)
.map(|end| end.min(bytes.len()))
.unwrap_or(bytes.len());
if search_end <= search_start + RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE {
return None;
}
let mut best_start = None;
let mut best_entries = Vec::new();
for candidate_start in search_start..search_end {
let entries = parse_direct_named_locomotive_entries(bytes, candidate_start, search_end);
if entries.len() > best_entries.len() {
best_entries = entries;
best_start = Some(candidate_start);
}
}
if best_entries.len() < RT3_105_SAVE_NAMED_LOCOMOTIVE_MIN_ENTRY_COUNT {
return None;
}
let entries_offset = best_start?;
let entries_end_offset = entries_offset
.checked_add(best_entries.len() * RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE)?;
let zero_availability_names = best_entries
.iter()
.filter(|entry| entry.availability_dword == 0)
.map(|entry| entry.text.clone())
.collect::<Vec<_>>();
let zero_availability_count = zero_availability_names.len();
let source_kind = match extension {
"gms" => "save-direct-locomotive-row-run",
"gmx" => "sandbox-direct-locomotive-row-run",
_ => "direct-locomotive-row-run",
}
.to_string();
let observed_entry_count = best_entries.len();
Some(SmpRt3105SaveNamedLocomotiveAvailabilityProbe {
profile_family,
source_kind,
semantic_family: "scenario-named-locomotive-availability-table".to_string(),
semantic_alignment: vec![
"Matches the grounded `.smp` save-side locomotive-name-plus-dword row family restored into scenario state [world+0x66b6].".to_string(),
"Entry layout is one availability dword at +0x00 followed by one fixed-width locomotive name buffer at +0x04..+0x40.".to_string(),
"The recovered row order is treated conservatively as the live locomotive ordinal order later used by locomotives-page descriptor lowering.".to_string(),
],
entries_offset,
entry_stride: RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE,
entry_stride_hex: format!("0x{:x}", RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE),
observed_entry_count,
zero_availability_count,
zero_availability_names,
entries_end_offset,
entries: best_entries,
evidence: vec![
format!("search span 0x{search_start:08x}..0x{search_end:08x}"),
format!("entries offset 0x{entries_offset:08x}"),
format!(
"entry stride 0x{:x}",
RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE
),
format!("observed entry count {observed_entry_count}"),
],
})
}
fn parse_direct_named_locomotive_entries(
bytes: &[u8],
start_offset: usize,
search_end: usize,
) -> Vec<SmpRt3105SaveNameTableEntry> {
let mut entries = Vec::new();
let mut offset = start_offset;
while offset + RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE <= bytes.len() && offset < search_end
{
let record = &bytes[offset..offset + RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE];
let Some(nul_index) = record[4..].iter().position(|byte| *byte == 0) else {
break;
};
let name_bytes = &record[4..4 + nul_index];
if name_bytes.is_empty() {
break;
}
let Ok(text) = std::str::from_utf8(name_bytes) else {
break;
};
if !is_probable_named_locomotive_label(text) {
break;
}
if record[4 + nul_index + 1..].iter().any(|byte| *byte != 0) {
break;
}
let availability_dword = u32::from_le_bytes([record[0], record[1], record[2], record[3]]);
entries.push(SmpRt3105SaveNameTableEntry {
index: entries.len(),
offset,
text: text.to_string(),
availability_dword,
availability_dword_hex: format!("0x{availability_dword:08x}"),
trailer_word: availability_dword,
trailer_word_hex: format!("0x{availability_dword:08x}"),
});
offset += RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE;
}
entries
}
fn is_probable_named_locomotive_label(text: &str) -> bool {
if text.is_empty() || text.len() > 40 {
return false;
}
text.bytes().all(|byte| {
byte.is_ascii_alphanumeric() || matches!(byte, b' ' | b'-' | b'/' | b'(' | b')' | b'.')
})
}
fn parse_special_conditions_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpSpecialConditionsProbe> {
let table_len = SPECIAL_CONDITION_COUNT.checked_mul(4)?;
let table_end = SPECIAL_CONDITIONS_OFFSET.checked_add(table_len)?;
if table_end > bytes.len() {
return None;
}
let mut entries = Vec::with_capacity(SPECIAL_CONDITION_COUNT);
for definition in KNOWN_SPECIAL_CONDITION_DEFINITIONS {
let value = read_u32_at(
bytes,
SPECIAL_CONDITIONS_OFFSET + (definition.slot_index as usize) * 4,
)?;
if value > 1 {
return None;
}
entries.push(SmpSpecialConditionEntry {
slot_index: definition.slot_index,
hidden: definition.hidden,
label_id: definition.label_id,
help_id: definition.help_id,
label: definition.label.to_string(),
value,
value_hex: format!("0x{value:08x}"),
});
}
let hidden_sentinel = entries
.iter()
.find(|entry| entry.slot_index == SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT as u8)?;
if hidden_sentinel.value != 1 {
return None;
}
let enabled_visible_labels = entries
.iter()
.filter(|entry| !entry.hidden && entry.value != 0)
.map(|entry| entry.label.clone())
.collect::<Vec<_>>();
let source_kind = match file_extension_hint.unwrap_or("") {
"gmp" => "map-fixed-special-conditions-range",
"gms" => "save-fixed-special-conditions-range",
"gmx" => "sandbox-fixed-special-conditions-range",
_ => "fixed-special-conditions-range",
}
.to_string();
let profile_family = container_profile
.map(|profile| profile.profile_family.clone())
.unwrap_or_else(|| "unknown".to_string());
let mut evidence = vec![
format!("fixed 36-dword range at 0x{SPECIAL_CONDITIONS_OFFSET:04x}"),
"all observed lanes are boolean dwords".to_string(),
"hidden slot 35 carries the expected sentinel value 1".to_string(),
"slot metadata matches the grounded editor special-conditions table at 0x005f3ab0"
.to_string(),
];
if enabled_visible_labels.is_empty() {
evidence.push("no visible special conditions enabled in this file".to_string());
} else {
evidence.push(format!(
"enabled visible conditions: {}",
enabled_visible_labels.join(", ")
));
}
Some(SmpSpecialConditionsProbe {
profile_family,
source_kind,
table_offset: SPECIAL_CONDITIONS_OFFSET,
table_len,
enabled_visible_count: enabled_visible_labels.len(),
enabled_visible_labels,
hidden_sentinel_slot_index: SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT as u8,
hidden_sentinel_value: hidden_sentinel.value,
hidden_sentinel_value_hex: hidden_sentinel.value_hex.clone(),
entries,
evidence,
})
}
fn parse_post_special_conditions_scalar_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
special_conditions_probe: Option<&SmpSpecialConditionsProbe>,
) -> Option<SmpPostSpecialConditionsScalarProbe> {
special_conditions_probe?;
if POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET > bytes.len() {
return None;
}
let dword_count =
(POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET - POST_SPECIAL_CONDITIONS_SCALAR_OFFSET) / 4;
let mut nonzero_lanes = Vec::new();
for index in 0..dword_count {
let absolute_offset = POST_SPECIAL_CONDITIONS_SCALAR_OFFSET + index * 4;
let value = read_u32_at(bytes, absolute_offset)?;
if value == 0 {
continue;
}
nonzero_lanes.push(SmpPostSpecialConditionsScalarLane {
absolute_offset,
relative_offset: absolute_offset - POST_SPECIAL_CONDITIONS_SCALAR_OFFSET,
absolute_offset_hex: format!("0x{absolute_offset:04x}"),
relative_offset_hex: format!(
"0x{:x}",
absolute_offset - POST_SPECIAL_CONDITIONS_SCALAR_OFFSET
),
value,
value_hex: format!("0x{value:08x}"),
probable_f32_le: probable_normal_f32_string(value),
});
}
let source_kind = match file_extension_hint.unwrap_or("") {
"gmp" => "map-post-special-conditions-window",
"gms" => "save-post-special-conditions-window",
"gmx" => "sandbox-post-special-conditions-window",
_ => "post-special-conditions-window",
}
.to_string();
let profile_family = container_profile
.map(|profile| profile.profile_family.clone())
.unwrap_or_else(|| "unknown".to_string());
let first_nonzero_offset = nonzero_lanes.first().map(|lane| lane.absolute_offset);
let last_nonzero_offset = nonzero_lanes.last().map(|lane| lane.absolute_offset);
let overlap_nonzero_relative_offset_hexes = nonzero_lanes
.iter()
.filter(|lane| lane.absolute_offset < POST_SPECIAL_CONDITIONS_SCALAR_OVERLAP_END_OFFSET)
.map(|lane| lane.relative_offset_hex.clone())
.collect::<Vec<_>>();
let tail_nonzero_lanes = nonzero_lanes
.iter()
.filter(|lane| lane.absolute_offset >= POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET)
.cloned()
.collect::<Vec<_>>();
let tail_first_nonzero_offset = tail_nonzero_lanes.first().map(|lane| lane.absolute_offset);
let tail_last_nonzero_offset = tail_nonzero_lanes.last().map(|lane| lane.absolute_offset);
let tail_nonzero_relative_offset_hexes = tail_nonzero_lanes
.iter()
.map(|lane| lane.relative_offset_hex.clone())
.collect::<Vec<_>>();
let grounded_text_field_remaining_file_window = &bytes[POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET
..POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET];
let mut grounded_text_field_remaining_nonzero_offsets = Vec::new();
for (index, byte) in grounded_text_field_remaining_file_window.iter().enumerate() {
if *byte != 0 {
grounded_text_field_remaining_nonzero_offsets
.push(POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET + index);
}
}
let grounded_text_field_remaining_first_nonzero_offset =
grounded_text_field_remaining_nonzero_offsets
.first()
.copied();
let grounded_text_field_remaining_last_nonzero_offset =
grounded_text_field_remaining_nonzero_offsets
.last()
.copied();
let mut evidence = vec![
format!(
"fixed post-sentinel dword window at 0x{POST_SPECIAL_CONDITIONS_SCALAR_OFFSET:04x}..0x{POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET:04x}"
),
"window starts immediately after the hidden special-conditions sentinel slot at 0x0df0"
.to_string(),
format!(
"leading overlap prefix 0x{POST_SPECIAL_CONDITIONS_SCALAR_OFFSET:04x}..0x{POST_SPECIAL_CONDITIONS_SCALAR_OVERLAP_END_OFFSET:04x} aliases aligned runtime-rule band indices {}..{}",
SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX,
SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX
+ SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT
- 1
),
format!("save-only tail begins at 0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET:04x}"),
format!(
"that tail is offset-aligned with live runtime object bytes [world+0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_OFFSET:04x}..+0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_END_OFFSET:04x}]"
),
format!(
"the tail start lands on the grounded live field [world+0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_OFFSET:04x}], a 0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_LEN:x}-byte status-text buffer written by win/lose and winner-announcement helpers"
),
format!(
"current dword scan stops at 0x{POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET:04x}, leaving one byte-aligned continuation window 0x{POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET:04x}..0x{POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET:04x} before the next clean live-field edge at [world+0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_END_OFFSET:04x}]"
),
format!(
"the next exact grounded fields after that edge begin at [world+0x{POST_TEXT_FIELD_0_RUNTIME_OBJECT_OFFSET:04x}], [world+0x{POST_TEXT_FIELD_1_RUNTIME_OBJECT_OFFSET:04x}], [world+0x{POST_TEXT_FIELD_2_RUNTIME_OBJECT_OFFSET:04x}], [world+0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_OFFSET:04x}], and [world+0x{POST_TEXT_FIELD_4_RUNTIME_OBJECT_OFFSET:04x}], which map to file offsets 0x{POST_TEXT_FIELD_NEIGHBORHOOD_OFFSET:04x}, 0x0f5d, 0x0f61, 0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_FILE_OFFSET:04x}, and 0x0f6d"
),
format!(
"the first grounded dword-sized fields after that edge are [world+0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_OFFSET:04x}] and [world+0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_OFFSET:04x}], which would land at file offsets 0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_FILE_OFFSET:04x} and 0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_FILE_OFFSET:04x}"
),
];
if nonzero_lanes.is_empty() {
evidence.push(
"all observed dwords in this post-sentinel window are zero for this file".to_string(),
);
} else {
evidence.push(format!(
"observed {} nonzero dword lanes between {} and {}",
nonzero_lanes.len(),
nonzero_lanes
.first()
.map(|lane| lane.absolute_offset_hex.as_str())
.unwrap_or("n/a"),
nonzero_lanes
.last()
.map(|lane| lane.absolute_offset_hex.as_str())
.unwrap_or("n/a")
));
if nonzero_lanes
.iter()
.all(|lane| lane.probable_f32_le.is_some())
{
evidence.push(
"every nonzero lane in this window also decodes as a normal finite little-endian f32"
.to_string(),
);
}
evidence.push(format!(
"{} nonzero lanes fall inside the aligned-band overlap prefix and {} fall inside the later tail",
overlap_nonzero_relative_offset_hexes.len(),
tail_nonzero_lanes.len()
));
}
evidence.push(
"checked file bytes in the later tail are not yet validated as a byte-for-byte mirror of the live object, because the region aligned to [world+0x4b47] does not currently decode as preserved text in the checked saves"
.to_string(),
);
if grounded_text_field_remaining_nonzero_offsets.is_empty() {
evidence.push(
"the remaining file window through the grounded text-field edge is all zero in this file"
.to_string(),
);
} else {
evidence.push(format!(
"the remaining file window through the grounded text-field edge still has {} nonzero bytes between 0x{:04x} and 0x{:04x}",
grounded_text_field_remaining_nonzero_offsets.len(),
grounded_text_field_remaining_first_nonzero_offset.unwrap_or(0),
grounded_text_field_remaining_last_nonzero_offset.unwrap_or(0)
));
}
Some(SmpPostSpecialConditionsScalarProbe {
profile_family,
source_kind,
window_offset: POST_SPECIAL_CONDITIONS_SCALAR_OFFSET,
window_end_offset: POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET,
window_len: POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET
- POST_SPECIAL_CONDITIONS_SCALAR_OFFSET,
window_len_hex: format!(
"0x{:x}",
POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET - POST_SPECIAL_CONDITIONS_SCALAR_OFFSET
),
dword_count,
overlap_end_offset: POST_SPECIAL_CONDITIONS_SCALAR_OVERLAP_END_OFFSET,
overlap_end_offset_hex: format!(
"0x{POST_SPECIAL_CONDITIONS_SCALAR_OVERLAP_END_OFFSET:04x}"
),
overlap_dword_count: (POST_SPECIAL_CONDITIONS_SCALAR_OVERLAP_END_OFFSET
- POST_SPECIAL_CONDITIONS_SCALAR_OFFSET)
/ 4,
overlap_nonzero_dword_count: overlap_nonzero_relative_offset_hexes.len(),
overlap_nonzero_relative_offset_hexes,
tail_offset: POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET,
tail_offset_hex: format!("0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET:04x}"),
tail_len: POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET
- POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET,
tail_len_hex: format!(
"0x{:x}",
POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET - POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET
),
tail_dword_count: (POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET
- POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET)
/ 4,
tail_runtime_object_offset: POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_OFFSET,
tail_runtime_object_offset_hex: format!(
"0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_OFFSET:04x}"
),
tail_runtime_object_end_offset:
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_END_OFFSET,
tail_runtime_object_end_offset_hex: format!(
"0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_END_OFFSET:04x}"
),
tail_runtime_object_validated_byte_mirror: false,
tail_grounded_live_field_offset:
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_OFFSET,
tail_grounded_live_field_offset_hex: format!(
"0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_OFFSET:04x}"
),
tail_grounded_live_field_name: "victory-or-outcome status text buffer".to_string(),
tail_grounded_live_field_copy_len:
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_LEN,
tail_grounded_live_field_copy_len_hex: format!(
"0x{:x}",
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_LEN
),
tail_grounded_live_field_copy_end_offset:
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_END_OFFSET,
tail_grounded_live_field_copy_end_offset_hex: format!(
"0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_END_OFFSET:04x}"
),
tail_window_cuts_through_grounded_live_field:
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_END_OFFSET
< POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_END_OFFSET,
tail_grounded_live_field_remaining_file_window_offset:
POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET,
tail_grounded_live_field_remaining_file_window_offset_hex: format!(
"0x{POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET:04x}"
),
tail_grounded_live_field_remaining_file_window_len:
POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET
- POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET,
tail_grounded_live_field_remaining_file_window_len_hex: format!(
"0x{:x}",
POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET
- POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET
),
tail_grounded_live_field_remaining_file_window_nonzero_byte_count:
grounded_text_field_remaining_nonzero_offsets.len(),
tail_grounded_live_field_remaining_file_window_first_nonzero_offset:
grounded_text_field_remaining_first_nonzero_offset,
tail_grounded_live_field_remaining_file_window_first_nonzero_offset_hex:
grounded_text_field_remaining_first_nonzero_offset
.map(|offset| format!("0x{offset:04x}")),
tail_grounded_live_field_remaining_file_window_last_nonzero_offset:
grounded_text_field_remaining_last_nonzero_offset,
tail_grounded_live_field_remaining_file_window_last_nonzero_offset_hex:
grounded_text_field_remaining_last_nonzero_offset
.map(|offset| format!("0x{offset:04x}")),
tail_next_grounded_dword_field_offset:
POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_OFFSET,
tail_next_grounded_dword_field_offset_hex: format!(
"0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_OFFSET:04x}"
),
tail_next_grounded_dword_field_file_offset:
POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_FILE_OFFSET,
tail_next_grounded_dword_field_file_offset_hex: format!(
"0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_FILE_OFFSET:04x}"
),
tail_second_grounded_dword_field_offset:
POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_OFFSET,
tail_second_grounded_dword_field_offset_hex: format!(
"0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_OFFSET:04x}"
),
tail_second_grounded_dword_field_file_offset:
POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_FILE_OFFSET,
tail_second_grounded_dword_field_file_offset_hex: format!(
"0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_FILE_OFFSET:04x}"
),
post_text_field_file_alignment_matches_grounded_dword_fields: false,
tail_nonzero_dword_count: tail_nonzero_lanes.len(),
tail_first_nonzero_offset,
tail_first_nonzero_offset_hex: tail_first_nonzero_offset
.map(|offset| format!("0x{offset:04x}")),
tail_last_nonzero_offset,
tail_last_nonzero_offset_hex: tail_last_nonzero_offset
.map(|offset| format!("0x{offset:04x}")),
tail_nonzero_relative_offset_hexes,
nonzero_dword_count: nonzero_lanes.len(),
first_nonzero_offset,
first_nonzero_offset_hex: first_nonzero_offset.map(|offset| format!("0x{offset:04x}")),
last_nonzero_offset,
last_nonzero_offset_hex: last_nonzero_offset.map(|offset| format!("0x{offset:04x}")),
nonzero_lanes,
evidence,
})
}
fn parse_post_text_field_neighborhood_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
special_conditions_probe: Option<&SmpSpecialConditionsProbe>,
) -> Option<SmpPostTextFieldNeighborhoodProbe> {
special_conditions_probe?;
if POST_TEXT_FIELD_NEIGHBORHOOD_END_OFFSET > bytes.len() {
return None;
}
let profile_family = container_profile
.map(|profile| profile.profile_family.clone())
.unwrap_or_else(|| "unknown".to_string());
let source_kind = match file_extension_hint.unwrap_or("") {
"gmp" => "post-text-grounded-field-neighborhood",
"gms" => "post-text-grounded-field-neighborhood",
"gmx" => "post-text-grounded-field-neighborhood",
_ => "post-text-grounded-field-neighborhood",
}
.to_string();
let exact_fields = [
(
"Auto-Show Grade During Track Lay",
POST_TEXT_FIELD_0_RUNTIME_OBJECT_OFFSET,
POST_TEXT_FIELD_NEIGHBORHOOD_OFFSET,
1usize,
),
(
"Starting Building Density Level",
POST_TEXT_FIELD_1_RUNTIME_OBJECT_OFFSET,
0x0f5dusize,
1usize,
),
(
"Building Density Growth",
POST_TEXT_FIELD_2_RUNTIME_OBJECT_OFFSET,
0x0f61usize,
1usize,
),
(
"leftover simulation time accumulator",
POST_TEXT_FIELD_3_RUNTIME_OBJECT_OFFSET,
0x0f65usize,
4usize,
),
(
"selected-year lane snapshot",
POST_TEXT_FIELD_4_RUNTIME_OBJECT_OFFSET,
0x0f6dusize,
1usize,
),
(
"late locomotive policy gate dword",
POST_TEXT_FIELD_5_RUNTIME_OBJECT_OFFSET,
0x0f71usize,
4usize,
),
];
let grounded_field_observations = exact_fields
.iter()
.map(
|(field_name, runtime_object_offset, file_offset, field_width_bytes)| {
let raw = &bytes[*file_offset..*file_offset + *field_width_bytes];
let raw_hex = hex_encode(raw);
let (value_u8, value_u8_hex, value_u32, value_u32_hex, probable_f32_le) =
if *field_width_bytes == 1 {
let value = raw[0];
(
Some(value),
Some(format!("0x{value:02x}")),
None,
None,
None,
)
} else {
let value = u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]);
(
None,
None,
Some(value),
Some(format!("0x{value:08x}")),
probable_normal_f32_string(value),
)
};
SmpPostTextGroundedFieldObservation {
field_name: (*field_name).to_string(),
runtime_object_offset: *runtime_object_offset,
runtime_object_offset_hex: format!("0x{runtime_object_offset:04x}"),
file_offset: *file_offset,
file_offset_hex: format!("0x{file_offset:04x}"),
field_width_bytes: *field_width_bytes,
field_width_bytes_hex: format!("0x{field_width_bytes:x}"),
raw_hex,
value_u8,
value_u8_hex,
value_u32,
value_u32_hex,
probable_f32_le,
}
},
)
.collect::<Vec<_>>();
let one_byte_early_float_candidates = exact_fields
.iter()
.filter(|(_, _, file_offset, _)| *file_offset > 0)
.filter_map(|(field_name, runtime_object_offset, file_offset, _)| {
let candidate_offset = file_offset - 1;
let value = read_u32_at(bytes, candidate_offset)?;
let probable_f32_le = probable_normal_f32_string(value)?;
Some(SmpPostTextFloatAlignmentCandidate {
grounded_field_name: (*field_name).to_string(),
grounded_field_runtime_object_offset: *runtime_object_offset,
grounded_field_runtime_object_offset_hex: format!("0x{runtime_object_offset:04x}"),
grounded_field_file_offset: *file_offset,
grounded_field_file_offset_hex: format!("0x{file_offset:04x}"),
candidate_offset,
candidate_offset_hex: format!("0x{candidate_offset:04x}"),
candidate_value: value,
candidate_value_hex: format!("0x{value:08x}"),
probable_f32_le,
})
})
.collect::<Vec<_>>();
let mut evidence = vec![
format!(
"post-text grounded-field neighborhood spans file offsets 0x{POST_TEXT_FIELD_NEIGHBORHOOD_OFFSET:04x}..0x{POST_TEXT_FIELD_NEIGHBORHOOD_END_OFFSET:04x}"
),
"this neighborhood starts at the first grounded post-text field [world+0x4c74] and extends through the later dword at [world+0x4c8c]".to_string(),
"the exact grounded field offsets here are byte-oriented at 0x0f59, 0x0f5d, 0x0f61, and 0x0f6d, with dword-sized fields only at 0x0f65 and 0x0f71".to_string(),
];
if one_byte_early_float_candidates.is_empty() {
evidence.push(
"no one-byte-early little-endian float-looking starts were observed ahead of the grounded fields in this file".to_string(),
);
} else {
evidence.push(format!(
"observed {} float-looking 4-byte starts exactly one byte before grounded field offsets in this file",
one_byte_early_float_candidates.len()
));
}
Some(SmpPostTextFieldNeighborhoodProbe {
profile_family,
source_kind,
window_offset: POST_TEXT_FIELD_NEIGHBORHOOD_OFFSET,
window_end_offset: POST_TEXT_FIELD_NEIGHBORHOOD_END_OFFSET,
window_len: POST_TEXT_FIELD_NEIGHBORHOOD_END_OFFSET - POST_TEXT_FIELD_NEIGHBORHOOD_OFFSET,
window_len_hex: format!(
"0x{:x}",
POST_TEXT_FIELD_NEIGHBORHOOD_END_OFFSET - POST_TEXT_FIELD_NEIGHBORHOOD_OFFSET
),
grounded_field_observations,
one_byte_early_float_candidates,
evidence,
})
}
fn parse_locomotive_policy_neighborhood_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
special_conditions_probe: Option<&SmpSpecialConditionsProbe>,
) -> Option<SmpLocomotivePolicyNeighborhoodProbe> {
special_conditions_probe?;
if LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET > bytes.len() {
return None;
}
let profile_family = container_profile
.map(|profile| profile.profile_family.clone())
.unwrap_or_else(|| "unknown".to_string());
let source_kind = match file_extension_hint.unwrap_or("") {
"gmp" => "locomotive-policy-neighborhood",
"gms" => "locomotive-policy-neighborhood",
"gmx" => "locomotive-policy-neighborhood",
_ => "locomotive-policy-neighborhood",
}
.to_string();
let exact_fields = [
(
"selected-year bucket companion scalar",
LOCOMOTIVE_POLICY_FIELD_NEG3_RUNTIME_OBJECT_OFFSET,
0x0f87usize,
4usize,
),
(
"startup-dispatch reset-owned band at +0x4cae",
LOCOMOTIVE_POLICY_FIELD_NEG2_RUNTIME_OBJECT_OFFSET,
0x0f93usize,
4usize,
),
(
"startup-dispatch reset-owned band at +0x4cb2",
LOCOMOTIVE_POLICY_FIELD_NEG1_RUNTIME_OBJECT_OFFSET,
0x0f97usize,
4usize,
),
(
"linked-site removal follow-on gate",
LOCOMOTIVE_POLICY_FIELD_0_RUNTIME_OBJECT_OFFSET,
0x0f78usize,
1usize,
),
(
"All Steam Locos Avail.",
LOCOMOTIVE_POLICY_FIELD_1_RUNTIME_OBJECT_OFFSET,
0x0f7cusize,
1usize,
),
(
"All Diesel Locos Avail.",
LOCOMOTIVE_POLICY_FIELD_2_RUNTIME_OBJECT_OFFSET,
0x0f7dusize,
1usize,
),
(
"All Electric Locos Avail.",
LOCOMOTIVE_POLICY_FIELD_3_RUNTIME_OBJECT_OFFSET,
0x0f7eusize,
1usize,
),
(
"station-list selected station id",
LOCOMOTIVE_POLICY_FIELD_4_RUNTIME_OBJECT_OFFSET,
0x0f9fusize,
4usize,
),
(
"cached available-locomotive rating",
LOCOMOTIVE_POLICY_FIELD_5_RUNTIME_OBJECT_OFFSET,
0x0fa3usize,
4usize,
),
];
let grounded_field_observations = exact_fields
.iter()
.map(
|(field_name, runtime_object_offset, file_offset, field_width_bytes)| {
let raw = &bytes[*file_offset..*file_offset + *field_width_bytes];
let raw_hex = hex_encode(raw);
let (value_u8, value_u8_hex, value_u32, value_u32_hex, probable_f32_le) =
if *field_width_bytes == 1 {
let value = raw[0];
(
Some(value),
Some(format!("0x{value:02x}")),
None,
None,
None,
)
} else {
let value = u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]);
(
None,
None,
Some(value),
Some(format!("0x{value:08x}")),
probable_normal_f32_string(value),
)
};
SmpLocomotivePolicyFieldObservation {
field_name: (*field_name).to_string(),
runtime_object_offset: *runtime_object_offset,
runtime_object_offset_hex: format!("0x{runtime_object_offset:04x}"),
file_offset: *file_offset,
file_offset_hex: format!("0x{file_offset:04x}"),
field_width_bytes: *field_width_bytes,
field_width_bytes_hex: format!("0x{field_width_bytes:x}"),
raw_hex,
value_u8,
value_u8_hex,
value_u32,
value_u32_hex,
probable_f32_le,
}
},
)
.collect::<Vec<_>>();
let three_byte_early_float_candidates = exact_fields
.iter()
.filter(|(_, _, _, width)| *width == 4usize)
.filter_map(|(field_name, runtime_object_offset, file_offset, _)| {
let candidate_offset = file_offset.saturating_sub(3);
let value = read_u32_at(bytes, candidate_offset)?;
let probable_f32_le = probable_normal_f32_string(value)?;
Some(SmpLocomotivePolicyFloatAlignmentCandidate {
grounded_field_name: (*field_name).to_string(),
grounded_field_runtime_object_offset: *runtime_object_offset,
grounded_field_runtime_object_offset_hex: format!("0x{runtime_object_offset:04x}"),
grounded_field_file_offset: *file_offset,
grounded_field_file_offset_hex: format!("0x{file_offset:04x}"),
candidate_offset,
candidate_offset_hex: format!("0x{candidate_offset:04x}"),
candidate_value: value,
candidate_value_hex: format!("0x{value:08x}"),
probable_f32_le,
})
})
.collect::<Vec<_>>();
let mut evidence = vec![
format!(
"locomotive-policy neighborhood spans file offsets 0x{LOCOMOTIVE_POLICY_NEIGHBORHOOD_OFFSET:04x}..0x{LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET:04x}"
),
"this neighborhood covers the selected-year bucket companion scalar, two startup-reset-owned bands, the linked-site removal gate, the three locomotive-availability policy bytes, the station-list selected-station mirror, and the cached available-locomotive rating".to_string(),
"the exact byte policy lanes live at 0x0f78 and 0x0f7c..0x0f7e, while the earlier grounded dword starts map to 0x0f87, 0x0f93, and 0x0f97 and the later grounded dword starts map to 0x0f9f and 0x0fa3".to_string(),
];
if three_byte_early_float_candidates.is_empty() {
evidence.push(
"no three-byte-early little-endian float-looking starts were observed ahead of the grounded dword fields in this file".to_string(),
);
} else {
evidence.push(format!(
"observed {} float-looking 4-byte starts exactly three bytes before grounded dword fields in this file",
three_byte_early_float_candidates.len()
));
}
Some(SmpLocomotivePolicyNeighborhoodProbe {
profile_family,
source_kind,
window_offset: LOCOMOTIVE_POLICY_NEIGHBORHOOD_OFFSET,
window_end_offset: LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET,
window_len: LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET
- LOCOMOTIVE_POLICY_NEIGHBORHOOD_OFFSET,
window_len_hex: format!(
"0x{:x}",
LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET - LOCOMOTIVE_POLICY_NEIGHBORHOOD_OFFSET
),
grounded_field_observations,
three_byte_early_float_candidates,
evidence,
})
}
fn parse_pre_recipe_scalar_plateau_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
special_conditions_probe: Option<&SmpSpecialConditionsProbe>,
) -> Option<SmpPreRecipeScalarPlateauProbe> {
special_conditions_probe?;
if PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET > bytes.len() {
return None;
}
let profile_family = container_profile
.map(|profile| profile.profile_family.clone())
.unwrap_or_else(|| "unknown".to_string());
let source_kind = match file_extension_hint.unwrap_or("") {
"gmp" => "pre-recipe-scalar-plateau",
"gms" => "pre-recipe-scalar-plateau",
"gmx" => "pre-recipe-scalar-plateau",
_ => "pre-recipe-scalar-plateau",
}
.to_string();
let aligned_dword_count =
(PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET - PRE_RECIPE_SCALAR_PLATEAU_OFFSET) / 4;
let mut nonzero_lanes = Vec::new();
for index in 0..aligned_dword_count {
let absolute_offset = PRE_RECIPE_SCALAR_PLATEAU_OFFSET + index * 4;
let value = read_u32_at(bytes, absolute_offset)?;
if value == 0 {
continue;
}
nonzero_lanes.push(SmpPreRecipeScalarPlateauLane {
absolute_offset,
relative_offset: absolute_offset - PRE_RECIPE_SCALAR_PLATEAU_OFFSET,
absolute_offset_hex: format!("0x{absolute_offset:04x}"),
relative_offset_hex: format!(
"0x{:x}",
absolute_offset - PRE_RECIPE_SCALAR_PLATEAU_OFFSET
),
value,
value_hex: format!("0x{value:08x}"),
probable_f32_le: probable_normal_f32_string(value),
});
}
let family_signature = match (
read_u32_at(bytes, 0x0faf),
read_u32_at(bytes, 0x0fb3),
read_u32_at(bytes, 0x0fcb),
) {
(Some(0x4000003f), Some(0xe560423f), Some(0x00000000)) => {
"rt3-105-scenario-pre-recipe-plateau-v1"
}
(Some(0x8000003f), Some(0x75c28f3f), Some(0x00300000)) => {
"rt3-105-base-pre-recipe-plateau-v1"
}
(Some(0x8000003f), Some(0x75c28f3f), Some(0xcdcdcd00)) => {
"rt3-105-alt-pre-recipe-plateau-v1"
}
_ => "unknown",
}
.to_string();
let mut evidence = vec![
format!(
"aligned scalar plateau spans file offsets 0x{PRE_RECIPE_SCALAR_PLATEAU_OFFSET:04x}..0x{PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET:04x}"
),
"this plateau ends immediately before the grounded recipe-book root at [world+0x0fe7]".to_string(),
"current grounding inside this span is still structural rather than semantic, so the probe only records aligned dword lanes and observed family signatures".to_string(),
];
if !nonzero_lanes.is_empty() {
evidence.push(format!(
"observed {} nonzero aligned dword lanes in the pre-recipe plateau",
nonzero_lanes.len()
));
}
if family_signature != "unknown" {
evidence.push(format!(
"matched observed family signature {family_signature}"
));
}
Some(SmpPreRecipeScalarPlateauProbe {
profile_family,
source_kind,
window_offset: PRE_RECIPE_SCALAR_PLATEAU_OFFSET,
window_end_offset: PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET,
window_len: PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET - PRE_RECIPE_SCALAR_PLATEAU_OFFSET,
window_len_hex: format!(
"0x{:x}",
PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET - PRE_RECIPE_SCALAR_PLATEAU_OFFSET
),
aligned_dword_count,
family_signature,
nonzero_lanes,
evidence,
})
}
fn parse_recipe_book_summary_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
special_conditions_probe: Option<&SmpSpecialConditionsProbe>,
) -> Option<SmpRecipeBookSummaryProbe> {
special_conditions_probe?;
if RECIPE_BOOK_SUMMARY_END_OFFSET > bytes.len() {
return None;
}
let profile_family = container_profile
.map(|profile| profile.profile_family.clone())
.unwrap_or_else(|| "unknown".to_string());
let source_kind = match file_extension_hint.unwrap_or("") {
"gmp" => "recipe-book-summary",
"gms" => "recipe-book-summary",
"gmx" => "recipe-book-summary",
_ => "recipe-book-summary",
}
.to_string();
let mut books = Vec::with_capacity(RECIPE_BOOK_COUNT);
let mut mixed_head_count = 0usize;
let mut mixed_line_area_count = 0usize;
let mut cdcd_line_area_count = 0usize;
let mut zero_line_area_count = 0usize;
for book_index in 0..RECIPE_BOOK_COUNT {
let book_offset = RECIPE_BOOK_ROOT_OFFSET + book_index * RECIPE_BOOK_STRIDE;
let head = &bytes[book_offset..book_offset + RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET];
let line_area_offset = book_offset + RECIPE_BOOK_LINE_AREA_OFFSET;
let line_area = &bytes[line_area_offset..line_area_offset + RECIPE_BOOK_LINE_AREA_LEN];
let max_annual_production_offset = book_offset + RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET;
let max_annual_production_word = read_u32_at(bytes, max_annual_production_offset)?;
let mut lines = Vec::with_capacity(RECIPE_BOOK_LINE_COUNT);
for line_index in 0..RECIPE_BOOK_LINE_COUNT {
let line_offset = line_area_offset + line_index * RECIPE_BOOK_LINE_STRIDE;
let line = &bytes[line_offset..line_offset + RECIPE_BOOK_LINE_STRIDE];
let supplied_cargo_token_window = &line[0x08..0x20];
let demanded_cargo_token_window = &line[0x1c..0x30];
let mode_word = read_u32_at(bytes, line_offset)?;
let annual_amount_word = read_u32_at(bytes, line_offset + 0x04)?;
let supplied_cargo_token_word = read_u32_at(bytes, line_offset + 0x08)?;
let demanded_cargo_token_word = read_u32_at(bytes, line_offset + 0x1c)?;
lines.push(SmpRecipeBookLineSummary {
line_index,
line_offset,
line_offset_hex: format!("0x{line_offset:04x}"),
line_kind: classify_recipe_book_region_kind(line).to_string(),
line_signature_kind: classify_recipe_line_signature(
mode_word,
supplied_cargo_token_word,
demanded_cargo_token_word,
)
.to_string(),
imports_to_runtime_descriptor: mode_word != 0,
runtime_import_branch_kind: classify_recipe_runtime_import_branch(mode_word)
.to_string(),
line_nonzero_byte_count: line.iter().filter(|byte| **byte != 0).count(),
line_cdcd_byte_count: line.iter().filter(|byte| **byte == 0xcd).count(),
line_first_16_hex: hex_encode(&line[..RECIPE_BOOK_HEAD_SAMPLE_LEN.min(line.len())]),
mode_word_offset: line_offset,
mode_word_offset_hex: format!("0x{line_offset:04x}"),
mode_word,
mode_word_hex: format!("0x{mode_word:08x}"),
annual_amount_offset: line_offset + 0x04,
annual_amount_offset_hex: format!("0x{:04x}", line_offset + 0x04),
annual_amount_word,
annual_amount_word_hex: format!("0x{annual_amount_word:08x}"),
annual_amount_probable_f32_le: probable_normal_f32_string(annual_amount_word),
supplied_cargo_token_offset: line_offset + 0x08,
supplied_cargo_token_offset_hex: format!("0x{:04x}", line_offset + 0x08),
supplied_cargo_token_word,
supplied_cargo_token_word_hex: format!("0x{supplied_cargo_token_word:08x}"),
supplied_cargo_token_layout_kind: classify_recipe_token_layout(
supplied_cargo_token_word,
)
.to_string(),
supplied_cargo_token_window_hex: hex_encode(supplied_cargo_token_window),
supplied_cargo_token_window_ascii: ascii_preview(supplied_cargo_token_window),
supplied_cargo_token_active_in_runtime_import: mode_word != 0 && mode_word != 1,
supplied_cargo_token_probable_high16_ascii_stem:
probable_recipe_token_high16_ascii_stem(supplied_cargo_token_word),
demanded_cargo_token_offset: line_offset + 0x1c,
demanded_cargo_token_offset_hex: format!("0x{:04x}", line_offset + 0x1c),
demanded_cargo_token_word,
demanded_cargo_token_word_hex: format!("0x{demanded_cargo_token_word:08x}"),
demanded_cargo_token_layout_kind: classify_recipe_token_layout(
demanded_cargo_token_word,
)
.to_string(),
demanded_cargo_token_window_hex: hex_encode(demanded_cargo_token_window),
demanded_cargo_token_window_ascii: ascii_preview(demanded_cargo_token_window),
demanded_cargo_token_active_in_runtime_import: mode_word == 1 || mode_word == 3,
demanded_cargo_token_probable_high16_ascii_stem:
probable_recipe_token_high16_ascii_stem(demanded_cargo_token_word),
});
}
let head_kind = classify_recipe_book_region_kind(head).to_string();
let line_area_kind = classify_recipe_book_region_kind(line_area).to_string();
if head_kind == "mixed" {
mixed_head_count += 1;
}
match line_area_kind.as_str() {
"zero" => zero_line_area_count += 1,
"cdcd" => cdcd_line_area_count += 1,
_ => mixed_line_area_count += 1,
}
books.push(SmpRecipeBookSummaryBook {
book_index,
book_offset,
book_offset_hex: format!("0x{book_offset:04x}"),
head_kind,
head_nonzero_byte_count: head.iter().filter(|byte| **byte != 0).count(),
head_cdcd_byte_count: head.iter().filter(|byte| **byte == 0xcd).count(),
head_first_16_hex: hex_encode(&head[..RECIPE_BOOK_HEAD_SAMPLE_LEN.min(head.len())]),
max_annual_production_offset,
max_annual_production_offset_hex: format!("0x{max_annual_production_offset:04x}"),
max_annual_production_word,
max_annual_production_word_hex: format!("0x{max_annual_production_word:08x}"),
max_annual_production_probable_f32_le: probable_normal_f32_string(
max_annual_production_word,
),
line_area_offset,
line_area_offset_hex: format!("0x{line_area_offset:04x}"),
line_area_len: RECIPE_BOOK_LINE_AREA_LEN,
line_area_len_hex: format!("0x{:x}", RECIPE_BOOK_LINE_AREA_LEN),
line_area_kind,
line_area_nonzero_byte_count: line_area.iter().filter(|byte| **byte != 0).count(),
line_area_cdcd_byte_count: line_area.iter().filter(|byte| **byte == 0xcd).count(),
line_area_first_16_hex: hex_encode(
&line_area[..RECIPE_BOOK_HEAD_SAMPLE_LEN.min(line_area.len())],
),
lines,
});
}
let mut evidence = vec![
format!(
"grounded recipe-book root begins at file offset 0x{RECIPE_BOOK_ROOT_OFFSET:04x} and runtime offset [world+0x{RECIPE_BOOK_ROOT_OFFSET:04x}]"
),
format!(
"parsed {RECIPE_BOOK_COUNT} fixed books with stride 0x{RECIPE_BOOK_STRIDE:x}, shared cap lane at +0x{RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET:x}, and five line slots at +0x{RECIPE_BOOK_LINE_AREA_OFFSET:x} with stride 0x{RECIPE_BOOK_LINE_STRIDE:x}"
),
"this probe is structural only: it summarizes per-book heads plus five raw line records without decoding the mode or cargo-token semantics beyond the grounded offsets".to_string(),
];
evidence.push(format!(
"{mixed_head_count} books have mixed pre-line heads; line areas split into {zero_line_area_count} zero, {cdcd_line_area_count} cdcd, and {mixed_line_area_count} mixed books"
));
Some(SmpRecipeBookSummaryProbe {
profile_family,
source_kind,
root_offset: RECIPE_BOOK_ROOT_OFFSET,
root_offset_hex: format!("0x{RECIPE_BOOK_ROOT_OFFSET:04x}"),
runtime_object_root_offset: RECIPE_BOOK_ROOT_OFFSET,
runtime_object_root_offset_hex: format!("0x{RECIPE_BOOK_ROOT_OFFSET:04x}"),
book_count: RECIPE_BOOK_COUNT,
book_stride: RECIPE_BOOK_STRIDE,
book_stride_hex: format!("0x{:x}", RECIPE_BOOK_STRIDE),
max_annual_production_relative_offset: RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET,
max_annual_production_relative_offset_hex: format!(
"0x{:x}",
RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET
),
line_area_relative_offset: RECIPE_BOOK_LINE_AREA_OFFSET,
line_area_relative_offset_hex: format!("0x{:x}", RECIPE_BOOK_LINE_AREA_OFFSET),
line_count: RECIPE_BOOK_LINE_COUNT,
line_stride: RECIPE_BOOK_LINE_STRIDE,
line_stride_hex: format!("0x{:x}", RECIPE_BOOK_LINE_STRIDE),
books,
evidence,
})
}
fn parse_smp_aligned_runtime_rule_band_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
special_conditions_probe: Option<&SmpSpecialConditionsProbe>,
) -> Option<SmpAlignedRuntimeRuleBandProbe> {
special_conditions_probe?;
if SMP_ALIGNED_RUNTIME_RULE_END_OFFSET > bytes.len() {
return None;
}
let source_kind = match file_extension_hint.unwrap_or("") {
"gmp" => "map-smp-aligned-runtime-rule-band",
"gms" => "save-smp-aligned-runtime-rule-band",
"gmx" => "sandbox-smp-aligned-runtime-rule-band",
_ => "smp-aligned-runtime-rule-band",
}
.to_string();
let profile_family = container_profile
.map(|profile| profile.profile_family.clone())
.unwrap_or_else(|| "unknown".to_string());
let mut nonzero_lanes = Vec::new();
for band_index in 0..SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT {
let absolute_offset = SPECIAL_CONDITIONS_OFFSET + band_index * 4;
let value = read_u32_at(bytes, absolute_offset)?;
if value == 0 {
continue;
}
let lane_kind = if band_index < SPECIAL_CONDITION_COUNT {
"known-special-condition-dword"
} else if band_index < SMP_ALIGNED_RUNTIME_RULE_KNOWN_EDITOR_RULE_COUNT {
"unlabeled-editor-rule-dword"
} else {
"trailing-runtime-scalar"
}
.to_string();
let known_label = if band_index < SPECIAL_CONDITION_COUNT {
Some(
KNOWN_SPECIAL_CONDITION_DEFINITIONS[band_index]
.label
.to_string(),
)
} else {
None
};
nonzero_lanes.push(SmpAlignedRuntimeRuleBandLane {
band_index,
absolute_offset,
relative_offset: absolute_offset - SPECIAL_CONDITIONS_OFFSET,
absolute_offset_hex: format!("0x{absolute_offset:04x}"),
relative_offset_hex: format!("0x{:x}", absolute_offset - SPECIAL_CONDITIONS_OFFSET),
lane_kind,
known_label,
value,
value_hex: format!("0x{value:08x}"),
probable_f32_le: probable_normal_f32_string(value),
});
}
let nonzero_band_indices = nonzero_lanes
.iter()
.map(|lane| lane.band_index)
.collect::<Vec<_>>();
let nonzero_post_window_overlap_band_indices = nonzero_lanes
.iter()
.filter(|lane| {
lane.band_index >= SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX
&& lane.band_index
< SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX
+ SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT
})
.map(|lane| lane.band_index)
.collect::<Vec<_>>();
let nonzero_post_window_overlap_post_relative_offset_hexes = nonzero_lanes
.iter()
.filter(|lane| {
lane.band_index >= SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX
&& lane.band_index
< SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX
+ SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT
})
.map(|lane| {
format!(
"0x{:x}",
(lane.band_index - SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX) * 4
)
})
.collect::<Vec<_>>();
let nonzero_relative_offset_hexes = nonzero_lanes
.iter()
.map(|lane| lane.relative_offset_hex.clone())
.collect::<Vec<_>>();
let mut evidence = vec![
format!(
"fixed `.smp`-aligned runtime-rule band at 0x{SPECIAL_CONDITIONS_OFFSET:04x}..0x{SMP_ALIGNED_RUNTIME_RULE_END_OFFSET:04x}"
),
format!(
"band spans {} known editor rule dwords plus one trailing scalar",
SMP_ALIGNED_RUNTIME_RULE_KNOWN_EDITOR_RULE_COUNT
),
"first 36 dwords overlap the older fixed matrix probe rooted at 0x0d64".to_string(),
format!(
"trailing band indices {}..{} alias the leading post-sentinel window offsets {}..{}",
SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX,
SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX
+ SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT
- 1,
"0x00",
format!(
"0x{:x}",
(SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT - 1) * 4
)
),
"band matches the grounded `.smp` save/load copy into `[world+0x4a7f..+0x4b43]`"
.to_string(),
];
if nonzero_lanes.is_empty() {
evidence
.push("all dwords in the aligned runtime-rule band are zero for this file".to_string());
} else {
evidence.push(format!(
"observed {} nonzero lanes at band indices {:?}",
nonzero_lanes.len(),
nonzero_band_indices
));
if !nonzero_post_window_overlap_band_indices.is_empty() {
evidence.push(format!(
"nonzero overlap lanes mirror post-window offsets {:?}",
nonzero_post_window_overlap_post_relative_offset_hexes
));
}
}
Some(SmpAlignedRuntimeRuleBandProbe {
profile_family,
source_kind,
band_offset: SPECIAL_CONDITIONS_OFFSET,
band_end_offset: SMP_ALIGNED_RUNTIME_RULE_END_OFFSET,
band_len: SMP_ALIGNED_RUNTIME_RULE_END_OFFSET - SPECIAL_CONDITIONS_OFFSET,
band_len_hex: format!(
"0x{:x}",
SMP_ALIGNED_RUNTIME_RULE_END_OFFSET - SPECIAL_CONDITIONS_OFFSET
),
dword_count: SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT,
known_editor_rule_dword_count: SMP_ALIGNED_RUNTIME_RULE_KNOWN_EDITOR_RULE_COUNT,
trailing_scalar_index: SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT - 1,
trailing_scalar_offset: SPECIAL_CONDITIONS_OFFSET
+ (SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT - 1) * 4,
trailing_scalar_offset_hex: format!(
"0x{:04x}",
SPECIAL_CONDITIONS_OFFSET + (SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT - 1) * 4
),
post_window_overlap_start_index: SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX,
post_window_overlap_dword_count: SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT,
post_window_overlap_end_index: SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX
+ SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT
- 1,
post_window_overlap_post_relative_offset_start_hex: "0x0".to_string(),
post_window_overlap_post_relative_offset_end_hex: format!(
"0x{:x}",
(SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT - 1) * 4
),
nonzero_post_window_overlap_band_indices,
nonzero_post_window_overlap_post_relative_offset_hexes,
nonzero_lane_count: nonzero_lanes.len(),
nonzero_band_indices,
nonzero_relative_offset_hexes,
nonzero_lanes,
evidence,
})
}
fn matches_candidate_availability_table_header(bytes: &[u8], header_offset: usize) -> bool {
matches!(
(
read_u32_at(bytes, header_offset + 0x08),
read_u32_at(bytes, header_offset + 0x0c),
read_u32_at(bytes, header_offset + 0x10),
read_u32_at(bytes, header_offset + 0x14),
read_u32_at(bytes, header_offset + 0x18),
read_u32_at(bytes, header_offset + 0x1c),
read_u32_at(bytes, header_offset + 0x20),
read_u32_at(bytes, header_offset + 0x24),
read_u32_at(bytes, header_offset + 0x28),
),
(
Some(0x0000332e),
Some(0x00000001),
Some(0x00000022),
Some(0x00000002),
Some(0x00000002),
Some(_),
Some(_),
Some(0x00000000),
Some(0x00000001),
)
)
}
fn classify_name_table_footer_progress_alignment(value: u32) -> Option<&'static str> {
match value {
0x32dc => Some(
"Footer progress word 0x000032dc matches the grounded late rehydrate progress id 0x32dc.",
),
0x3714 => Some(
"Footer progress word 0x00003714 matches the grounded late rehydrate progress id 0x3714.",
),
_ => None,
}
}
fn parse_rt3_105_packed_profile_block(
bytes: &[u8],
packed_profile_offset: usize,
packed_profile_len: usize,
) -> Option<SmpRt3105PackedProfileBlock> {
let block_end = packed_profile_offset.checked_add(packed_profile_len)?;
if block_end > bytes.len() || packed_profile_len != 0x108 {
return None;
}
let leading_word_0 = read_u32_at(bytes, packed_profile_offset)?;
let trailing_zero_word_count_after_leading_word = (1..4)
.take_while(|index| {
read_u32_at(bytes, packed_profile_offset + (index * 4)).is_some_and(|word| word == 0)
})
.count();
let header_flag_word_3 = read_u32_at(bytes, packed_profile_offset + 0x0c)?;
let map_path_offset = 0x10usize;
let display_name_offset = 0x43usize;
let stable_nonzero_word_offsets = [0x00usize, 0x0c, 0x78, 0x7c, 0x80, 0x84];
let stable_nonzero_words = stable_nonzero_word_offsets
.iter()
.filter_map(|relative_offset| {
let value = read_u32_at(bytes, packed_profile_offset + relative_offset)?;
if value == 0 {
return None;
}
Some(SmpPackedProfileWordLane {
relative_offset: *relative_offset,
relative_offset_hex: format!("0x{relative_offset:02x}"),
value,
value_hex: format!("0x{value:08x}"),
})
})
.collect::<Vec<_>>();
Some(SmpRt3105PackedProfileBlock {
relative_len: packed_profile_len,
relative_len_hex: format!("0x{packed_profile_len:03x}"),
leading_word_0,
leading_word_0_hex: format!("0x{leading_word_0:08x}"),
trailing_zero_word_count_after_leading_word,
header_flag_word_3,
header_flag_word_3_hex: format!("0x{header_flag_word_3:08x}"),
map_path_offset,
map_path: read_c_string_in_range(bytes, packed_profile_offset + map_path_offset, block_end),
display_name_offset,
display_name: read_c_string_in_range(
bytes,
packed_profile_offset + display_name_offset,
block_end,
),
profile_byte_0x77: bytes[packed_profile_offset + 0x77],
profile_byte_0x77_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0x77]),
profile_byte_0x82: bytes[packed_profile_offset + 0x82],
profile_byte_0x82_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0x82]),
profile_byte_0x97: bytes[packed_profile_offset + 0x97],
profile_byte_0x97_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0x97]),
profile_byte_0xc5: bytes[packed_profile_offset + 0xc5],
profile_byte_0xc5_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0xc5]),
stable_nonzero_words,
})
}
fn collect_runtime_post_span_header_candidates(
bytes: &[u8],
start: usize,
search_len: usize,
) -> Vec<SmpRuntimePostSpanHeaderCandidate> {
let end = bytes.len().min(start + search_len);
let mut offset = start & !0x3;
let mut candidates = Vec::new();
while offset + 16 <= end && candidates.len() < 8 {
if let Some(candidate) = build_runtime_post_span_header_candidate(bytes, offset) {
let mut cluster_end = offset + 4;
while cluster_end + 16 <= end
&& build_runtime_post_span_header_candidate(bytes, cluster_end).is_some()
{
cluster_end += 4;
}
candidates.push(candidate);
offset = cluster_end;
} else {
offset += 4;
}
}
candidates
}
fn build_runtime_post_span_header_candidate(
bytes: &[u8],
offset: usize,
) -> Option<SmpRuntimePostSpanHeaderCandidate> {
let words = read_u32_window(bytes, offset, 4);
if words.len() < 4 {
return None;
}
let dense_words = words
.iter()
.copied()
.filter(|word| (word & 0xffff) == 0 && (word >> 16) != 0)
.collect::<Vec<_>>();
if dense_words.len() < 3 {
return None;
}
let high_u16_words = words
.iter()
.map(|word| (word >> 16) as u16)
.collect::<Vec<_>>();
let mut grounded_alignments = Vec::new();
for high in &high_u16_words {
if let Some(alignment) = classify_runtime_post_span_high16_grounded_alignment(*high) {
if !grounded_alignments
.iter()
.any(|existing| existing == alignment)
{
grounded_alignments.push(alignment.to_string());
}
}
}
Some(SmpRuntimePostSpanHeaderCandidate {
offset,
hex_words: words.iter().map(|word| format!("0x{word:08x}")).collect(),
dense_word_count: dense_words.len(),
high_hex_words: high_u16_words
.iter()
.map(|word| format!("0x{word:04x}"))
.collect(),
high_u16_words,
grounded_alignments,
words,
})
}
fn classify_runtime_post_span_high16_grounded_alignment(high_u16: u16) -> Option<&'static str> {
match high_u16 {
0x32dc => Some(
"High-16 value 0x32dc matches the grounded late rehydrate progress id posted during world_entry_transition_and_runtime_bringup.",
),
0x3714 => Some(
"High-16 value 0x3714 matches the grounded late rehydrate progress id posted during world_entry_transition_and_runtime_bringup.",
),
0x3715 => Some(
"High-16 value 0x3715 matches the grounded late rehydrate progress id posted during world_entry_transition_and_runtime_bringup.",
),
_ => None,
}
}
fn find_grounded_progress_high16_hits(
bytes: &[u8],
start: usize,
search_len: usize,
) -> Vec<String> {
let end = bytes.len().min(start + search_len);
let mut hits = Vec::new();
let mut offset = start & !0x3;
while offset + 4 <= end {
if let Some(word) = read_u32_at(bytes, offset) {
let high = (word >> 16) as u16;
if matches!(high, 0x32dc | 0x3714 | 0x3715) {
hits.push(format!("0x{high:04x}@0x{offset:08x}"));
}
}
offset += 4;
}
hits
}
fn parse_grounded_progress_hit_offset(hits: &[String], high_u16: u16) -> Option<usize> {
let needle = format!("0x{high_u16:04x}@0x");
let hit = hits.iter().find(|hit| hit.starts_with(&needle))?;
let offset_hex = hit.split("@0x").nth(1)?;
usize::from_str_radix(offset_hex, 16).ok()
}
fn collect_ascii_previews_in_range(
bytes: &[u8],
start: usize,
end: usize,
min_len: usize,
) -> Vec<SmpAsciiPreview> {
let mut previews = Vec::new();
let mut run_start = None;
let end = end.min(bytes.len());
for index in start..end {
let byte = bytes[index];
if is_ascii_preview_byte(byte) {
run_start.get_or_insert(index);
continue;
}
if let Some(current_start) = run_start.take() {
if index - current_start >= min_len {
previews.push(build_ascii_preview(bytes, current_start, index));
}
}
}
if let Some(current_start) = run_start {
if end - current_start >= min_len {
previews.push(build_ascii_preview(bytes, current_start, end));
}
}
previews
}
fn find_c_string_with_suffix_in_range(
bytes: &[u8],
start: usize,
end: usize,
suffix: &str,
) -> Option<usize> {
let end = end.min(bytes.len());
let suffix = suffix.as_bytes();
let mut offset = start.min(end);
while offset < end {
if !is_ascii_preview_byte(bytes[offset]) {
offset += 1;
continue;
}
let run_start = offset;
while offset < end && is_ascii_preview_byte(bytes[offset]) {
offset += 1;
}
let run = &bytes[run_start..offset];
if run.ends_with(suffix) {
return Some(run_start);
}
}
None
}
fn read_c_string_in_range(bytes: &[u8], start: usize, end: usize) -> Option<String> {
if start >= end || start >= bytes.len() {
return None;
}
let end = end.min(bytes.len());
let mut cursor = start;
while cursor < end && bytes[cursor] != 0 {
cursor += 1;
}
if cursor == start {
return None;
}
std::str::from_utf8(&bytes[start..cursor])
.ok()
.map(ToString::to_string)
}
fn find_u16_le_offsets(bytes: &[u8], needle: u16) -> Vec<usize> {
let pattern = needle.to_le_bytes();
bytes
.windows(pattern.len())
.enumerate()
.filter_map(|(offset, window)| (window == pattern).then_some(offset))
.collect()
}
fn find_u32_le_offsets(bytes: &[u8], needle: u32) -> Vec<usize> {
let pattern = needle.to_le_bytes();
bytes
.windows(pattern.len())
.enumerate()
.filter_map(|(offset, window)| (window == pattern).then_some(offset))
.collect()
}
fn find_next_nonzero_offset(bytes: &[u8], start: usize) -> Option<usize> {
bytes
.iter()
.enumerate()
.skip(start)
.find_map(|(offset, byte)| (*byte != 0).then_some(offset))
}
fn find_zero_run(bytes: &[u8], start: usize, min_len: usize) -> Option<usize> {
let mut run_start = None;
let mut run_len = 0usize;
for (offset, byte) in bytes.iter().enumerate().skip(start) {
if *byte == 0 {
run_start.get_or_insert(offset);
run_len += 1;
if run_len >= min_len {
return run_start;
}
} else {
run_start = None;
run_len = 0;
}
}
None
}
fn find_first_ascii_run(bytes: &[u8]) -> Option<SmpAsciiPreview> {
let mut start = None;
for (index, byte) in bytes.iter().copied().enumerate() {
if is_ascii_preview_byte(byte) {
start.get_or_insert(index);
continue;
}
if let Some(run_start) = start.take() {
if index - run_start >= MIN_ASCII_RUN_LEN {
return Some(build_ascii_preview(bytes, run_start, index));
}
}
}
start.and_then(|run_start| {
if bytes.len() - run_start >= MIN_ASCII_RUN_LEN {
Some(build_ascii_preview(bytes, run_start, bytes.len()))
} else {
None
}
})
}
fn build_ascii_preview(bytes: &[u8], start: usize, end: usize) -> SmpAsciiPreview {
let byte_len = end - start;
let preview_bytes = &bytes[start..end];
let preview = String::from_utf8_lossy(
&preview_bytes[..preview_bytes.len().min(ASCII_PREVIEW_CHAR_LIMIT)],
)
.into_owned();
SmpAsciiPreview {
offset: start,
byte_len,
truncated: byte_len > ASCII_PREVIEW_CHAR_LIMIT,
preview,
}
}
fn is_ascii_preview_byte(byte: u8) -> bool {
matches!(byte, b' ' | b'\t' | b'\n' | b'\r' | 0x21..=0x7e)
}
fn read_u32_window(bytes: &[u8], offset: usize, count: usize) -> Vec<u32> {
let mut words = Vec::new();
let end = bytes.len().min(offset + count * 4);
for chunk in bytes[offset..end].chunks_exact(4) {
words.push(u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
}
words
}
fn read_u8_at(bytes: &[u8], offset: usize) -> Option<u8> {
bytes.get(offset).copied()
}
fn read_u16_at(bytes: &[u8], offset: usize) -> Option<u16> {
let chunk = bytes.get(offset..offset + 2)?;
Some(u16::from_le_bytes([chunk[0], chunk[1]]))
}
fn read_u32_at(bytes: &[u8], offset: usize) -> Option<u32> {
let chunk = bytes.get(offset..offset + 4)?;
Some(u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
}
fn read_i32_at(bytes: &[u8], offset: usize) -> Option<i32> {
let chunk = bytes.get(offset..offset + 4)?;
Some(i32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
}
fn read_i64_at(bytes: &[u8], offset: usize) -> Option<i64> {
let chunk = bytes.get(offset..offset + 8)?;
Some(i64::from_le_bytes([
chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
]))
}
fn read_u64_at(bytes: &[u8], offset: usize) -> Option<u64> {
let chunk = bytes.get(offset..offset + 8)?;
Some(u64::from_le_bytes([
chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
]))
}
fn read_f32_at(bytes: &[u8], offset: usize) -> Option<f32> {
let chunk = bytes.get(offset..offset + 4)?;
Some(f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
}
fn read_f64_at(bytes: &[u8], offset: usize) -> Option<f64> {
let chunk = bytes.get(offset..offset + 8)?;
Some(f64::from_le_bytes([
chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
]))
}
fn read_ascii_c_string_at(bytes: &[u8], offset: usize, max_len: usize) -> Option<String> {
let chunk = bytes.get(offset..offset + max_len)?;
let nul_index = chunk.iter().position(|byte| *byte == 0).unwrap_or(max_len);
let text = std::str::from_utf8(&chunk[..nul_index])
.ok()?
.trim()
.to_string();
Some(text)
}
fn parse_nonzero_u32(bytes: &[u8], offset: usize) -> Option<Option<u32>> {
read_u32_at(bytes, offset).map(|value| (value != 0).then_some(value))
}
fn round_f64_to_i64(value: f64) -> Option<i64> {
if !value.is_finite() {
return None;
}
let rounded = value.round();
if rounded < i64::MIN as f64 || rounded > i64::MAX as f64 {
return None;
}
Some(rounded as i64)
}
fn probable_normal_f32_string(value: u32) -> Option<String> {
let exponent = (value >> 23) & 0xff;
if exponent == 0 || exponent == 0xff {
return None;
}
let scalar = f32::from_bits(value);
if !scalar.is_finite() {
return None;
}
Some(format!("{scalar:.6}"))
}
fn probable_recipe_token_high16_ascii_stem(value: u32) -> Option<String> {
if value & 0xffff != 0 {
return None;
}
let high = ((value >> 16) & 0xffff) as u16;
if high == 0 {
return None;
}
let low_byte = (high & 0x00ff) as u8;
let high_byte = (high >> 8) as u8;
if !low_byte.is_ascii_alphabetic() || !high_byte.is_ascii_alphabetic() {
return None;
}
Some(format!("{}{}", low_byte as char, high_byte as char))
}
fn classify_recipe_token_layout(value: u32) -> &'static str {
if value == 0 {
return "zero";
}
if probable_recipe_token_high16_ascii_stem(value).is_some() {
return "high16-ascii-stem";
}
if value & 0xffff == 0 {
return "high16-numeric";
}
if value >> 16 == 0 {
return "low16-marker";
}
"mixed"
}
fn classify_recipe_line_signature(
mode_word: u32,
supplied_cargo_token_word: u32,
demanded_cargo_token_word: u32,
) -> &'static str {
let supplied_layout = classify_recipe_token_layout(supplied_cargo_token_word);
let demanded_layout = classify_recipe_token_layout(demanded_cargo_token_word);
if mode_word == 0 && supplied_cargo_token_word == 0 && demanded_layout == "high16-numeric" {
return "demand-numeric-entry";
}
if mode_word == 0 && supplied_cargo_token_word == 0 && demanded_layout == "high16-ascii-stem" {
return "demand-stem-entry";
}
if mode_word == 0 && demanded_cargo_token_word == 0 && supplied_layout == "high16-numeric" {
return "supply-numeric-entry";
}
if mode_word != 0 && demanded_cargo_token_word == 0 && supplied_layout == "low16-marker" {
return "supply-marker-entry";
}
if mode_word == 0 && supplied_cargo_token_word == 0 && demanded_cargo_token_word == 0 {
return "zero";
}
"mixed"
}
fn classify_recipe_runtime_import_branch(mode_word: u32) -> &'static str {
if mode_word == 0 {
return "zero-mode-skipped";
}
if mode_word == 1 {
return "mode1-demand-branch";
}
if mode_word == 3 {
return "mode3-dual-branch";
}
"nonzero-supply-branch"
}
fn classify_recipe_book_region_kind(bytes: &[u8]) -> &'static str {
if bytes.iter().all(|byte| *byte == 0) {
"zero"
} else if bytes.iter().all(|byte| *byte == 0xcd) {
"cdcd"
} else {
"mixed"
}
}
fn hex_encode(bytes: &[u8]) -> String {
bytes.iter().map(|byte| format!("{byte:02x}")).collect()
}
fn ascii_preview(bytes: &[u8]) -> String {
bytes
.iter()
.map(|byte| match byte {
0x20..=0x7e => char::from(*byte),
_ => '.',
})
.collect()
}
fn sha256_hex(bytes: &[u8]) -> String {
let digest = Sha256::digest(bytes);
format!("{digest:x}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reports_grounded_tag_hits_and_offsets() {
let bytes = [
0x34, 0x12, 0x00, 0x00, 0xe0, 0x2e, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x80,
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x71, 0x07, 0x00, 0x00, 0x71, 0x07, 0x00, 0x00,
0x71, 0x07, 0x00, 0x00, 0xaa, 0xbb, 0x00, 0x00, 0xcc, 0xdd, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, b'H', b'e', b'l',
b'l', b'o', b' ', b'R', b'R', b'T', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78,
0x9a, 0xbc, 0xde, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xee, 0x2c, 0x11, 0x51, 0x2d, 0x22,
0x71, 0x94, 0x33, 0x72, 0x94,
];
let report = inspect_smp_bytes(&bytes);
assert!(report.contains_grounded_runtime_tags);
assert_eq!(report.known_tag_hits.len(), 4);
assert_eq!(report.preamble.word_count, 16);
assert_eq!(report.preamble.words[0].value_le, 0x00001234);
let shared_header = report
.shared_header
.as_ref()
.expect("shared header should parse");
assert!(shared_header.matches_grounded_common_signature);
let header_variant = report
.header_variant_probe
.as_ref()
.expect("header variant probe should exist");
assert_eq!(header_variant.variant_family, "unknown");
assert!(!header_variant.is_known_family);
assert_eq!(shared_header.primary_family_tag, 0x00002ee0);
assert_eq!(
shared_header.payload_window_words_8_to_9,
vec![0x0000bbaa, 0x0000ddcc]
);
assert!(shared_header.reserved_words_10_to_14_all_zero);
assert_eq!(shared_header.final_flag_word, 0);
let ascii_run = report
.first_ascii_run
.as_ref()
.expect("ascii run should exist");
assert_eq!(ascii_run.offset, 67);
assert_eq!(ascii_run.byte_len, 9);
assert_eq!(ascii_run.preview, "Hello RRT");
let early_probe = report
.early_content_probe
.as_ref()
.expect("early content probe should exist");
assert_eq!(early_probe.first_post_text_nonzero_offset, 88);
assert_eq!(early_probe.zero_pad_after_text_len, 12);
assert_eq!(early_probe.first_post_text_block_len, 4);
assert_eq!(early_probe.first_post_text_block_hex, "11223344");
assert_eq!(early_probe.trailing_zero_pad_after_first_block_len, 16);
assert_eq!(early_probe.secondary_nonzero_offset, Some(108));
assert_eq!(early_probe.secondary_aligned_word_window_offset, Some(108));
assert_eq!(
&early_probe.secondary_aligned_word_window_words[..2],
&[0x78563412, 0xf0debc9a]
);
assert!(
early_probe
.secondary_preview_hex
.starts_with("123456789abcdef0")
);
let secondary_variant = report
.secondary_variant_probe
.as_ref()
.expect("secondary variant probe should exist");
assert_eq!(secondary_variant.variant_family, "unknown");
let container_profile = report
.container_profile
.as_ref()
.expect("container profile should exist");
assert_eq!(container_profile.profile_family, "unknown");
assert!(!container_profile.is_known_profile);
assert!(report.save_bootstrap_block.is_none());
assert!(report.save_anchor_run_block.is_none());
assert!(report.runtime_anchor_cycle_block.is_none());
assert!(report.runtime_trailer_block.is_none());
assert!(report.runtime_post_span_probe.is_none());
assert!(report.classic_rehydrate_profile_probe.is_none());
assert_eq!(report.known_tag_hits[0].tag_id, 0x2cee);
assert_eq!(report.known_tag_hits[0].hit_count, 1);
assert_eq!(report.known_tag_hits[0].sample_offsets, vec![120]);
assert_eq!(report.known_tag_hits[1].tag_id, 0x2d51);
assert_eq!(report.known_tag_hits[1].sample_offsets, vec![123]);
assert_eq!(report.known_tag_hits[2].tag_id, 0x9471);
assert_eq!(report.known_tag_hits[2].sample_offsets, vec![126]);
assert_eq!(report.known_tag_hits[3].tag_id, 0x9472);
assert_eq!(report.known_tag_hits[3].sample_offsets, vec![129]);
}
#[test]
fn warns_when_no_grounded_tags_are_present() {
let report = inspect_smp_bytes(&[0xaa, 0xbb, 0xcc]);
assert!(!report.contains_grounded_runtime_tags);
assert!(report.known_tag_hits.is_empty());
assert_eq!(report.preamble.word_count, 0);
assert!(report.shared_header.is_none());
assert!(report.header_variant_probe.is_none());
assert!(report.first_ascii_run.is_none());
assert!(report.early_content_probe.is_none());
assert!(report.secondary_variant_probe.is_none());
assert!(report.container_profile.is_none());
assert!(report.save_bootstrap_block.is_none());
assert!(report.save_anchor_run_block.is_none());
assert!(report.runtime_anchor_cycle_block.is_none());
assert!(report.runtime_trailer_block.is_none());
assert!(report.runtime_post_span_probe.is_none());
assert!(report.classic_rehydrate_profile_probe.is_none());
assert!(
report
.warnings
.iter()
.any(|warning| warning.contains("No grounded runtime bundle tags were found"))
);
}
#[test]
fn parses_zeroed_post_special_conditions_scalar_window() {
let mut bytes = vec![0u8; POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET];
let sentinel_offset =
SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4;
bytes[sentinel_offset..sentinel_offset + 4].copy_from_slice(&1u32.to_le_bytes());
let special_conditions_probe = parse_special_conditions_probe(&bytes, Some("gmp"), None)
.expect("special-conditions probe should parse");
let probe = parse_post_special_conditions_scalar_probe(
&bytes,
Some("gmp"),
Some(&SmpContainerProfile {
profile_family: "rt3-map-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
Some(&special_conditions_probe),
)
.expect("post-special-conditions probe should parse");
assert_eq!(probe.window_offset, POST_SPECIAL_CONDITIONS_SCALAR_OFFSET);
assert_eq!(
probe.window_end_offset,
POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET
);
assert_eq!(probe.dword_count, 79);
assert_eq!(
probe.overlap_end_offset,
POST_SPECIAL_CONDITIONS_SCALAR_OVERLAP_END_OFFSET
);
assert_eq!(probe.overlap_dword_count, 14);
assert_eq!(probe.overlap_nonzero_dword_count, 0);
assert!(probe.overlap_nonzero_relative_offset_hexes.is_empty());
assert_eq!(
probe.tail_offset,
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET
);
assert_eq!(probe.tail_dword_count, 65);
assert_eq!(
probe.tail_runtime_object_offset,
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_OFFSET
);
assert_eq!(
probe.tail_runtime_object_end_offset,
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_END_OFFSET
);
assert!(!probe.tail_runtime_object_validated_byte_mirror);
assert_eq!(
probe.tail_grounded_live_field_offset,
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_OFFSET
);
assert_eq!(
probe.tail_grounded_live_field_copy_len,
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_LEN
);
assert_eq!(
probe.tail_grounded_live_field_copy_end_offset,
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_END_OFFSET
);
assert!(probe.tail_window_cuts_through_grounded_live_field);
assert_eq!(
probe.tail_grounded_live_field_remaining_file_window_offset,
POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET
);
assert_eq!(
probe.tail_grounded_live_field_remaining_file_window_len,
0x28
);
assert_eq!(
probe.tail_grounded_live_field_remaining_file_window_nonzero_byte_count,
0
);
assert_eq!(probe.tail_next_grounded_dword_field_offset_hex, "0x4c80");
assert_eq!(
probe.tail_next_grounded_dword_field_file_offset_hex,
"0x0f65"
);
assert_eq!(probe.tail_second_grounded_dword_field_offset_hex, "0x4c8c");
assert_eq!(
probe.tail_second_grounded_dword_field_file_offset_hex,
"0x0f71"
);
assert!(!probe.post_text_field_file_alignment_matches_grounded_dword_fields);
assert!(
probe
.tail_grounded_live_field_remaining_file_window_first_nonzero_offset
.is_none()
);
assert!(
probe
.tail_grounded_live_field_remaining_file_window_last_nonzero_offset
.is_none()
);
assert_eq!(probe.tail_nonzero_dword_count, 0);
assert!(probe.tail_first_nonzero_offset.is_none());
assert!(probe.tail_last_nonzero_offset.is_none());
assert!(probe.tail_nonzero_relative_offset_hexes.is_empty());
assert_eq!(probe.nonzero_dword_count, 0);
assert!(probe.first_nonzero_offset.is_none());
assert!(probe.last_nonzero_offset.is_none());
assert!(probe.nonzero_lanes.is_empty());
}
#[test]
fn parses_zeroed_smp_aligned_runtime_rule_band() {
let mut bytes = vec![0u8; POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET];
let sentinel_offset =
SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4;
bytes[sentinel_offset..sentinel_offset + 4].copy_from_slice(&1u32.to_le_bytes());
let special_conditions_probe = parse_special_conditions_probe(&bytes, Some("gmp"), None)
.expect("special-conditions probe should parse");
let probe = parse_smp_aligned_runtime_rule_band_probe(
&bytes,
Some("gmp"),
Some(&SmpContainerProfile {
profile_family: "rt3-map-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
Some(&special_conditions_probe),
)
.expect("aligned runtime-rule band probe should parse");
assert_eq!(probe.band_offset, SPECIAL_CONDITIONS_OFFSET);
assert_eq!(probe.band_end_offset, SMP_ALIGNED_RUNTIME_RULE_END_OFFSET);
assert_eq!(probe.dword_count, SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT);
assert_eq!(
probe.known_editor_rule_dword_count,
SMP_ALIGNED_RUNTIME_RULE_KNOWN_EDITOR_RULE_COUNT
);
assert_eq!(
probe.post_window_overlap_start_index,
SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX
);
assert_eq!(
probe.post_window_overlap_dword_count,
SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT
);
assert_eq!(probe.nonzero_lane_count, 1);
assert_eq!(probe.nonzero_band_indices, vec![35]);
assert!(probe.nonzero_post_window_overlap_band_indices.is_empty());
assert!(
probe
.nonzero_post_window_overlap_post_relative_offset_hexes
.is_empty()
);
assert_eq!(
probe.nonzero_lanes[0].lane_kind,
"known-special-condition-dword"
);
assert_eq!(
probe.nonzero_lanes[0].known_label.as_deref(),
Some("Hidden sentinel")
);
}
#[test]
fn parses_nonzero_post_special_conditions_scalar_window() {
let mut bytes = vec![0u8; POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET];
let sentinel_offset =
SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4;
bytes[sentinel_offset..sentinel_offset + 4].copy_from_slice(&1u32.to_le_bytes());
bytes[0x0df8..0x0dfc].copy_from_slice(&0x413d298cu32.to_le_bytes());
bytes[0x0e00..0x0e04].copy_from_slice(&0x40e6b756u32.to_le_bytes());
bytes[0x0f0c..0x0f10].copy_from_slice(&0x42574909u32.to_le_bytes());
bytes[0x0f34] = 0xaa;
bytes[0x0f4e] = 0xbb;
let special_conditions_probe = parse_special_conditions_probe(&bytes, Some("gms"), None)
.expect("special-conditions probe should parse");
let probe = parse_post_special_conditions_scalar_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
Some(&special_conditions_probe),
)
.expect("post-special-conditions probe should parse");
assert_eq!(probe.nonzero_dword_count, 3);
assert_eq!(probe.first_nonzero_offset, Some(0x0df8));
assert_eq!(probe.last_nonzero_offset, Some(0x0f0c));
assert_eq!(probe.overlap_nonzero_dword_count, 2);
assert_eq!(
probe.overlap_nonzero_relative_offset_hexes,
vec!["0x4".to_string(), "0xc".to_string()]
);
assert_eq!(probe.tail_nonzero_dword_count, 1);
assert_eq!(probe.tail_first_nonzero_offset, Some(0x0f0c));
assert_eq!(probe.tail_last_nonzero_offset, Some(0x0f0c));
assert_eq!(
probe.tail_nonzero_relative_offset_hexes,
vec!["0x118".to_string()]
);
assert_eq!(probe.tail_runtime_object_offset_hex, "0x4b47");
assert_eq!(probe.tail_runtime_object_end_offset_hex, "0x4c4b");
assert_eq!(
probe.tail_grounded_live_field_copy_end_offset_hex,
"0x4c73".to_string()
);
assert_eq!(
probe.tail_grounded_live_field_name,
"victory-or-outcome status text buffer"
);
assert!(probe.tail_window_cuts_through_grounded_live_field);
assert_eq!(
probe.tail_grounded_live_field_remaining_file_window_len_hex,
"0x28"
);
assert_eq!(
probe.tail_grounded_live_field_remaining_file_window_nonzero_byte_count,
2
);
assert_eq!(
probe.tail_grounded_live_field_remaining_file_window_first_nonzero_offset,
Some(0x0f34)
);
assert_eq!(
probe.tail_grounded_live_field_remaining_file_window_last_nonzero_offset,
Some(0x0f4e)
);
assert_eq!(probe.tail_next_grounded_dword_field_file_offset, 0x0f65);
assert_eq!(probe.tail_second_grounded_dword_field_file_offset, 0x0f71);
assert!(!probe.post_text_field_file_alignment_matches_grounded_dword_fields);
assert_eq!(probe.nonzero_lanes[0].relative_offset, 0x04);
assert_eq!(probe.nonzero_lanes[1].relative_offset, 0x0c);
assert_eq!(probe.nonzero_lanes[2].relative_offset, 0x118);
assert!(
probe
.nonzero_lanes
.iter()
.all(|lane| lane.probable_f32_le.is_some())
);
}
#[test]
fn parses_post_text_field_neighborhood_probe() {
let mut bytes = vec![0u8; POST_TEXT_FIELD_NEIGHBORHOOD_END_OFFSET];
let sentinel_offset =
SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4;
bytes[sentinel_offset..sentinel_offset + 4].copy_from_slice(&1u32.to_le_bytes());
bytes[0x0f59] = 0x01;
bytes[0x0f5d] = 0x02;
bytes[0x0f61] = 0x03;
bytes[0x0f6d] = 0x04;
bytes[0x0f5c..0x0f60].copy_from_slice(&0x40f33333u32.to_le_bytes());
bytes[0x0f6c..0x0f70].copy_from_slice(&0x40c08cfbu32.to_le_bytes());
let special_conditions_probe = parse_special_conditions_probe(&bytes, Some("gms"), None)
.expect("special-conditions probe should parse");
let probe = parse_post_text_field_neighborhood_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-scenario-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
Some(&special_conditions_probe),
)
.expect("post-text field neighborhood probe should parse");
assert_eq!(probe.window_offset, POST_TEXT_FIELD_NEIGHBORHOOD_OFFSET);
assert_eq!(
probe.window_end_offset,
POST_TEXT_FIELD_NEIGHBORHOOD_END_OFFSET
);
assert_eq!(probe.grounded_field_observations.len(), 6);
assert_eq!(
probe.grounded_field_observations[0].field_name,
"Auto-Show Grade During Track Lay"
);
assert_eq!(probe.grounded_field_observations[0].value_u8, Some(0x01));
assert_eq!(
probe.grounded_field_observations[3].field_name,
"leftover simulation time accumulator"
);
assert_eq!(probe.one_byte_early_float_candidates.len(), 2);
assert_eq!(
probe.one_byte_early_float_candidates[0].grounded_field_name,
"Starting Building Density Level"
);
assert_eq!(
probe.one_byte_early_float_candidates[0].candidate_offset_hex,
"0x0f5c"
);
assert_eq!(
probe.one_byte_early_float_candidates[1].grounded_field_name,
"selected-year lane snapshot"
);
assert_eq!(
probe.one_byte_early_float_candidates[1].candidate_offset_hex,
"0x0f6c"
);
}
#[test]
fn parses_locomotive_policy_neighborhood_probe() {
let mut bytes = vec![0u8; LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET];
let sentinel_offset =
SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4;
bytes[sentinel_offset..sentinel_offset + 4].copy_from_slice(&1u32.to_le_bytes());
bytes[0x0f78] = 0x01;
bytes[0x0f7c] = 0x02;
bytes[0x0f7d] = 0x03;
bytes[0x0f7e] = 0x04;
bytes[0x0f9c..0x0fa0].copy_from_slice(&0x42c1c036u32.to_le_bytes());
bytes[0x0fa0..0x0fa4].copy_from_slice(&0x433a7abeu32.to_le_bytes());
let special_conditions_probe = parse_special_conditions_probe(&bytes, Some("gms"), None)
.expect("special-conditions probe should parse");
let probe = parse_locomotive_policy_neighborhood_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-scenario-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
Some(&special_conditions_probe),
)
.expect("locomotive policy neighborhood probe should parse");
assert_eq!(probe.window_offset, LOCOMOTIVE_POLICY_NEIGHBORHOOD_OFFSET);
assert_eq!(
probe.window_end_offset,
LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET
);
assert_eq!(probe.grounded_field_observations.len(), 9);
assert_eq!(
probe.grounded_field_observations[0].field_name,
"selected-year bucket companion scalar"
);
assert_eq!(
probe.grounded_field_observations[4].field_name,
"All Steam Locos Avail."
);
assert_eq!(probe.grounded_field_observations[4].value_u8, Some(0x02));
assert_eq!(
probe.grounded_field_observations[8].field_name,
"cached available-locomotive rating"
);
assert_eq!(probe.three_byte_early_float_candidates.len(), 2);
assert_eq!(
probe.three_byte_early_float_candidates[0].grounded_field_name,
"station-list selected station id"
);
assert_eq!(
probe.three_byte_early_float_candidates[0].candidate_offset_hex,
"0x0f9c"
);
assert_eq!(
probe.three_byte_early_float_candidates[1].grounded_field_name,
"cached available-locomotive rating"
);
assert_eq!(
probe.three_byte_early_float_candidates[1].candidate_offset_hex,
"0x0fa0"
);
}
#[test]
fn parses_pre_recipe_scalar_plateau_probe() {
let mut bytes = vec![0u8; PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET];
let sentinel_offset =
SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4;
bytes[sentinel_offset..sentinel_offset + 4].copy_from_slice(&1u32.to_le_bytes());
bytes[0x0fa7..0x0fab].copy_from_slice(&0x82839300u32.to_le_bytes());
bytes[0x0fab..0x0faf].copy_from_slice(&0x948c9949u32.to_le_bytes());
bytes[0x0faf..0x0fb3].copy_from_slice(&0x8000003fu32.to_le_bytes());
bytes[0x0fb3..0x0fb7].copy_from_slice(&0x75c28f3fu32.to_le_bytes());
bytes[0x0fcb..0x0fcf].copy_from_slice(&0x00300000u32.to_le_bytes());
bytes[0x0fdb..0x0fdf].copy_from_slice(&0x00ffea22u32.to_le_bytes());
let special_conditions_probe = parse_special_conditions_probe(&bytes, Some("gms"), None)
.expect("special-conditions probe should parse");
let probe = parse_pre_recipe_scalar_plateau_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
Some(&special_conditions_probe),
)
.expect("pre-recipe scalar plateau probe should parse");
assert_eq!(probe.window_offset, PRE_RECIPE_SCALAR_PLATEAU_OFFSET);
assert_eq!(
probe.window_end_offset,
PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET
);
assert_eq!(probe.family_signature, "rt3-105-base-pre-recipe-plateau-v1");
assert_eq!(probe.nonzero_lanes[0].absolute_offset_hex, "0x0fa7");
assert_eq!(probe.nonzero_lanes[2].absolute_offset_hex, "0x0faf");
assert_eq!(probe.nonzero_lanes[2].value_hex, "0x8000003f");
}
#[test]
fn parses_recipe_book_summary_probe() {
let mut bytes = vec![0u8; RECIPE_BOOK_SUMMARY_END_OFFSET];
let sentinel_offset =
SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4;
bytes[sentinel_offset..sentinel_offset + 4].copy_from_slice(&1u32.to_le_bytes());
let book0 = RECIPE_BOOK_ROOT_OFFSET;
bytes[book0..book0 + 16].copy_from_slice(&[
0x11, 0x22, 0x33, 0x44, 0xaa, 0xbb, 0xcc, 0xdd, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60,
0x70, 0x80,
]);
bytes[book0 + RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET
..book0 + RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET + 4]
.copy_from_slice(&0x41200000u32.to_le_bytes());
bytes[book0 + RECIPE_BOOK_LINE_AREA_OFFSET
..book0 + RECIPE_BOOK_LINE_AREA_OFFSET + RECIPE_BOOK_LINE_AREA_LEN]
.fill(0xcd);
bytes[book0 + RECIPE_BOOK_LINE_AREA_OFFSET..book0 + RECIPE_BOOK_LINE_AREA_OFFSET + 4]
.copy_from_slice(&0x00000003u32.to_le_bytes());
bytes[book0 + RECIPE_BOOK_LINE_AREA_OFFSET + 4..book0 + RECIPE_BOOK_LINE_AREA_OFFSET + 8]
.copy_from_slice(&0x41a00000u32.to_le_bytes());
bytes[book0 + RECIPE_BOOK_LINE_AREA_OFFSET + 8..book0 + RECIPE_BOOK_LINE_AREA_OFFSET + 12]
.copy_from_slice(&0x00000017u32.to_le_bytes());
bytes[book0 + RECIPE_BOOK_LINE_AREA_OFFSET + 0x1c
..book0 + RECIPE_BOOK_LINE_AREA_OFFSET + 0x20]
.copy_from_slice(&0x0000002au32.to_le_bytes());
let book1 = RECIPE_BOOK_ROOT_OFFSET + RECIPE_BOOK_STRIDE;
bytes[book1 + RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET
..book1 + RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET + 4]
.copy_from_slice(&0x00000000u32.to_le_bytes());
let special_conditions_probe = parse_special_conditions_probe(&bytes, Some("gmp"), None)
.expect("special-conditions probe should parse");
let probe = parse_recipe_book_summary_probe(
&bytes,
Some("gmp"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-map-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
Some(&special_conditions_probe),
)
.expect("recipe-book summary probe should parse");
assert_eq!(probe.root_offset, RECIPE_BOOK_ROOT_OFFSET);
assert_eq!(probe.book_count, RECIPE_BOOK_COUNT);
assert_eq!(probe.book_stride, RECIPE_BOOK_STRIDE);
assert_eq!(
probe.max_annual_production_relative_offset,
RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET
);
assert_eq!(probe.books[0].head_kind, "mixed");
assert_eq!(probe.books[0].line_area_kind, "mixed");
assert_eq!(probe.books[0].max_annual_production_word_hex, "0x41200000");
assert_eq!(
probe.books[0]
.max_annual_production_probable_f32_le
.as_deref(),
Some("10.000000")
);
assert_eq!(probe.books[0].lines.len(), RECIPE_BOOK_LINE_COUNT);
assert_eq!(probe.books[0].lines[0].line_kind, "mixed");
assert_eq!(probe.books[0].lines[0].mode_word_hex, "0x00000003");
assert_eq!(probe.books[0].lines[0].annual_amount_word_hex, "0x41a00000");
assert_eq!(
probe.books[0].lines[0]
.annual_amount_probable_f32_le
.as_deref(),
Some("20.000000")
);
assert_eq!(
probe.books[0].lines[0].supplied_cargo_token_word_hex,
"0x00000017"
);
assert_eq!(
probe.books[0].lines[0].supplied_cargo_token_window_hex,
"17000000cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd2a000000"
);
assert_eq!(
probe.books[0].lines[0].supplied_cargo_token_window_ascii,
"....................*..."
);
assert!(probe.books[0].lines[0].supplied_cargo_token_active_in_runtime_import);
assert_eq!(
probe.books[0].lines[0].demanded_cargo_token_word_hex,
"0x0000002a"
);
assert_eq!(
probe.books[0].lines[0].demanded_cargo_token_window_hex,
"2a000000cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"
);
assert_eq!(
probe.books[0].lines[0].demanded_cargo_token_window_ascii,
"*..................."
);
assert!(probe.books[0].lines[0].demanded_cargo_token_active_in_runtime_import);
assert_eq!(probe.books[1].head_kind, "zero");
assert_eq!(probe.books[1].line_area_kind, "zero");
assert_eq!(probe.books[1].lines[0].line_kind, "zero");
}
#[test]
fn decodes_probable_recipe_token_high16_ascii_stem() {
assert_eq!(
probable_recipe_token_high16_ascii_stem(0x72470000).as_deref(),
Some("Gr")
);
assert_eq!(
probable_recipe_token_high16_ascii_stem(0x68430000).as_deref(),
Some("Ch")
);
assert_eq!(probable_recipe_token_high16_ascii_stem(0x000040a0), None);
assert_eq!(probable_recipe_token_high16_ascii_stem(0x00170000), None);
}
#[test]
fn classifies_recipe_token_layouts() {
assert_eq!(classify_recipe_token_layout(0x00000000), "zero");
assert_eq!(
classify_recipe_token_layout(0x72470000),
"high16-ascii-stem"
);
assert_eq!(classify_recipe_token_layout(0x00170000), "high16-numeric");
assert_eq!(classify_recipe_token_layout(0x000040a0), "low16-marker");
}
#[test]
fn classifies_recipe_line_signatures() {
assert_eq!(
classify_recipe_line_signature(0x00000000, 0x00000000, 0x00010000),
"demand-numeric-entry"
);
assert_eq!(
classify_recipe_line_signature(0x00000000, 0x00000000, 0x72470000),
"demand-stem-entry"
);
assert_eq!(
classify_recipe_line_signature(0x00000000, 0x00170000, 0x00000000),
"supply-numeric-entry"
);
assert_eq!(
classify_recipe_line_signature(0x00110000, 0x000040a0, 0x00000000),
"supply-marker-entry"
);
}
#[test]
fn classifies_recipe_runtime_import_branches() {
assert_eq!(
classify_recipe_runtime_import_branch(0),
"zero-mode-skipped"
);
assert_eq!(
classify_recipe_runtime_import_branch(1),
"mode1-demand-branch"
);
assert_eq!(
classify_recipe_runtime_import_branch(3),
"mode3-dual-branch"
);
assert_eq!(
classify_recipe_runtime_import_branch(0x00110000),
"nonzero-supply-branch"
);
}
#[test]
fn parses_nonzero_smp_aligned_runtime_rule_band() {
let mut bytes = vec![0u8; POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET];
let sentinel_offset =
SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4;
bytes[sentinel_offset..sentinel_offset + 4].copy_from_slice(&1u32.to_le_bytes());
bytes[0x0df8..0x0dfc].copy_from_slice(&0x413d298cu32.to_le_bytes());
bytes[0x0e00..0x0e04].copy_from_slice(&0x40e6b756u32.to_le_bytes());
bytes[0x0e18..0x0e1c].copy_from_slice(&0x41d4ccceu32.to_le_bytes());
bytes[0x0e24..0x0e28].copy_from_slice(&0x3fd2b549u32.to_le_bytes());
let special_conditions_probe = parse_special_conditions_probe(&bytes, Some("gms"), None)
.expect("special-conditions probe should parse");
let probe = parse_smp_aligned_runtime_rule_band_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-scenario-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
Some(&special_conditions_probe),
)
.expect("aligned runtime-rule band probe should parse");
assert_eq!(probe.nonzero_band_indices, vec![35, 37, 39, 45, 48]);
assert_eq!(
probe.nonzero_post_window_overlap_band_indices,
vec![37, 39, 45, 48]
);
assert_eq!(
probe.nonzero_post_window_overlap_post_relative_offset_hexes,
vec![
"0x4".to_string(),
"0xc".to_string(),
"0x24".to_string(),
"0x30".to_string()
]
);
assert_eq!(probe.nonzero_lanes[1].relative_offset, 0x94);
assert_eq!(
probe.nonzero_lanes[1].lane_kind,
"unlabeled-editor-rule-dword"
);
assert!(probe.nonzero_lanes[1].probable_f32_le.is_some());
assert_eq!(probe.nonzero_lanes.last().unwrap().band_index, 48);
}
#[test]
fn parses_save_anchor_cycle_and_trailer() {
let cycle_words: [u32; 9] = [
0x00000000, 0x0186a000, 0x00000000, 0x86a00000, 0x00000001, 0xa0000000, 0x00000186,
0x00000000, 0x000186a0,
];
let trailer_words: [u32; 3] = [0x00020000, 0x00030000, 0x2ee10000];
let mut bytes = vec![0u8; 0x1c + (cycle_words.len() * 2 + 2 + trailer_words.len()) * 4];
let mut cursor = 0x1c;
for _ in 0..2 {
for word in cycle_words {
bytes[cursor..cursor + 4].copy_from_slice(&word.to_le_bytes());
cursor += 4;
}
}
for word in &cycle_words[..2] {
bytes[cursor..cursor + 4].copy_from_slice(&(*word).to_le_bytes());
cursor += 4;
}
for word in trailer_words {
bytes[cursor..cursor + 4].copy_from_slice(&word.to_le_bytes());
cursor += 4;
}
let container_profile = SmpContainerProfile {
profile_family: "rt3-classic-save-container-v1".to_string(),
profile_evidence: vec!["test".to_string()],
is_known_profile: true,
};
let bootstrap = SmpSaveBootstrapBlock {
profile_family: "rt3-classic-save-container-v1".to_string(),
aligned_window_offset: 0,
leading_word: 0,
leading_word_hex: "0x00000000".to_string(),
anchor_word: 0,
anchor_word_hex: "0x00000000".to_string(),
descriptor_word_2: 0,
descriptor_word_2_hex: "0x00000000".to_string(),
descriptor_word_3: 0,
descriptor_word_3_hex: "0x00000000".to_string(),
descriptor_word_4: 0,
descriptor_word_4_hex: "0x00000000".to_string(),
descriptor_word_5: 0,
descriptor_word_5_hex: "0x00000000".to_string(),
descriptor_word_6: 0,
descriptor_word_6_hex: "0x00000000".to_string(),
descriptor_word_7: 0,
descriptor_word_7_hex: "0x00000000".to_string(),
};
let parsed =
parse_save_anchor_run_block(&bytes, Some(&container_profile), Some(&bootstrap))
.expect("cycle block should parse");
assert_eq!(parsed.cycle_start_offset, 0x1c);
assert_eq!(parsed.cycle_words, cycle_words);
assert_eq!(parsed.full_cycle_count, 2);
assert_eq!(parsed.partial_cycle_word_count, 2);
assert_eq!(
parsed.trailer_offset,
0x1c + (cycle_words.len() * 2 + 2) * 4
);
assert_eq!(parsed.trailer_words, trailer_words);
}
#[test]
fn classifies_runtime_trailer_family() {
let runtime_anchor_cycle_block = SmpRuntimeAnchorCycleBlock {
profile_family: "rt3-classic-sandbox-container-v1".to_string(),
cycle_start_offset: 0x33c,
cycle_words: vec![0; 9],
cycle_hex_words: vec!["0x00000000".to_string(); 9],
full_cycle_count: 3,
partial_cycle_word_count: 2,
trailer_offset: 0x3b0,
trailer_words: vec![
0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00000000, 0x00000000, 0x2ee10000,
0x32c80000, 0x0dcd0000, 0x01010107, 0x26010000, 0x01010107, 0x00010000, 0x0334c68c,
0x03000000, 0x01000000,
],
trailer_hex_words: Vec::new(),
};
let container_profile = SmpContainerProfile {
profile_family: "rt3-classic-sandbox-container-v1".to_string(),
profile_evidence: vec!["test".to_string()],
is_known_profile: true,
};
let trailer = parse_runtime_trailer_block(
Some(&container_profile),
Some(&runtime_anchor_cycle_block),
)
.expect("runtime trailer should parse");
assert_eq!(trailer.trailer_family, "rt3-classic-sandbox-trailer-v1");
assert_eq!(trailer.prefix_words_0_to_5[0], 0x00010000);
assert_eq!(trailer.tag_word_6, 0x2ee10000);
assert_eq!(trailer.tag_chunk_id_u16, 0x2ee1);
assert_eq!(trailer.selector_word_8, 0x0dcd0000);
assert_eq!(trailer.selector_high_u16, 0x0dcd);
assert_eq!(trailer.mode_word_15, 0x01000000);
}
#[test]
fn probes_runtime_post_span_region() {
let mut bytes = vec![0u8; 0x200];
bytes[0x90..0x94].copy_from_slice(&0x32dc0000u32.to_le_bytes());
bytes[0x94..0x98].copy_from_slice(&0x37140000u32.to_le_bytes());
bytes[0x98..0x9c].copy_from_slice(&0x03000000u32.to_le_bytes());
bytes[0xa0..0xa4].copy_from_slice(&0x37150000u32.to_le_bytes());
bytes[0xa4..0xa8].copy_from_slice(&0x00010000u32.to_le_bytes());
bytes[0xa8..0xac].copy_from_slice(&0x00410000u32.to_le_bytes());
let trailer = SmpRuntimeTrailerBlock {
profile_family: "rt3-classic-save-container-v1".to_string(),
trailer_family: "test".to_string(),
trailer_evidence: Vec::new(),
trailer_offset: 0x40,
prefix_words_0_to_5: Vec::new(),
prefix_hex_words_0_to_5: Vec::new(),
tag_word_6: 0x2ee10000,
tag_word_6_hex: "0x2ee10000".to_string(),
tag_chunk_id_u16: 0x2ee1,
tag_chunk_id_hex: "0x2ee1".to_string(),
tag_chunk_id_grounded_alignment: None,
length_word_7: 0x00200000,
length_word_7_hex: "0x00200000".to_string(),
length_high_u16: 0x0020,
length_high_hex: "0x0020".to_string(),
selector_word_8: 0,
selector_word_8_hex: "0x00000000".to_string(),
selector_high_u16: 0,
selector_high_hex: "0x0000".to_string(),
layout_word_9: 0,
layout_word_9_hex: "0x00000000".to_string(),
descriptor_word_10: 0,
descriptor_word_10_hex: "0x00000000".to_string(),
descriptor_high_u16: 0,
descriptor_high_hex: "0x0000".to_string(),
descriptor_word_11: 0,
descriptor_word_11_hex: "0x00000000".to_string(),
counter_word_12: 0,
counter_word_12_hex: "0x00000000".to_string(),
offset_word_13: 0,
offset_word_13_hex: "0x00000000".to_string(),
span_word_14: 0,
span_word_14_hex: "0x00000000".to_string(),
mode_word_15: 0,
mode_word_15_hex: "0x00000000".to_string(),
words: Vec::new(),
hex_words: Vec::new(),
};
let probe = parse_runtime_post_span_probe(&bytes, Some(&trailer))
.expect("post-span probe should parse");
assert_eq!(probe.span_target_offset, 0x60);
assert_eq!(probe.next_nonzero_offset, Some(0x92));
assert_eq!(probe.next_aligned_candidate_offset, Some(0x8c));
assert_eq!(probe.header_candidates.len(), 1);
assert_eq!(probe.header_candidates[0].dense_word_count, 3);
assert_eq!(probe.header_candidates[0].grounded_alignments.len(), 2);
assert_eq!(probe.grounded_progress_hits[0], "0x32dc@0x00000090");
}
#[test]
fn parses_classic_rehydrate_profile_probe() {
let mut bytes = vec![0u8; 0x220];
bytes[0x90..0x94].copy_from_slice(&0x32dc0000u32.to_le_bytes());
bytes[0x94..0x98].copy_from_slice(&0x37140000u32.to_le_bytes());
bytes[0x1a0..0x1a4].copy_from_slice(&0x37150000u32.to_le_bytes());
bytes[0xab..0xb7].copy_from_slice(b"test-map.gmp");
bytes[0xde..0xe6].copy_from_slice(b"Test Map");
let post_span = SmpRuntimePostSpanProbe {
profile_family: "rt3-classic-save-container-v1".to_string(),
span_target_offset: 0,
next_nonzero_offset: Some(0x92),
next_aligned_candidate_offset: Some(0x8c),
next_aligned_candidate_words: vec![0, 0x32dc0000, 0x37140000, 0x03000000],
next_aligned_candidate_hex_words: vec![],
header_candidates: vec![],
grounded_progress_hits: vec![
"0x32dc@0x00000090".to_string(),
"0x3714@0x00000094".to_string(),
"0x3715@0x000001a0".to_string(),
],
};
let probe = parse_classic_rehydrate_profile_probe(&bytes, Some(&post_span))
.expect("classic rehydrate probe should parse");
assert_eq!(probe.packed_profile_offset, 0x98);
assert_eq!(probe.packed_profile_len, 0x108);
assert_eq!(probe.ascii_runs[0].preview, "test-map.gmp");
assert_eq!(probe.packed_profile_block.leading_word_0, 0x00000000);
assert_eq!(
probe.packed_profile_block.map_path.as_deref(),
Some("test-map.gmp")
);
assert_eq!(
probe.packed_profile_block.display_name.as_deref(),
Some("Test Map")
);
assert_eq!(probe.packed_profile_block.profile_byte_0x77, 0x00);
assert_eq!(probe.packed_profile_block.profile_byte_0x82, 0x00);
assert_eq!(probe.packed_profile_block.profile_byte_0x97, 0x00);
assert_eq!(probe.packed_profile_block.profile_byte_0xc5, 0x00);
}
#[test]
fn parses_rt3_105_packed_profile_probe() {
let mut bytes = vec![0u8; 0x9000];
let block = 0x73c0usize;
bytes[block..block + 4].copy_from_slice(&0x00000003u32.to_le_bytes());
bytes[block + 0x0c..block + 0x10].copy_from_slice(&0x01000000u32.to_le_bytes());
bytes[block + 0x10..block + 0x1d].copy_from_slice(b"test-105.gmp\0");
bytes[block + 0x43..block + 0x4c].copy_from_slice(b"Test 105\0");
bytes[block + 0x77] = 0x07;
bytes[block + 0x82] = 0x4d;
bytes[block + 0x84..block + 0x88].copy_from_slice(&0x65010000u32.to_le_bytes());
let header_variant_probe = SmpHeaderVariantProbe {
variant_family: "rt3-105-common-header-v1".to_string(),
variant_evidence: vec![],
is_known_family: true,
};
let probe = parse_rt3_105_packed_profile_probe(
&bytes,
Some("gms"),
Some(&header_variant_probe),
None,
)
.expect("1.05 packed profile probe should parse");
assert_eq!(probe.profile_family, "rt3-105-save-analog-block-inferred");
assert_eq!(probe.packed_profile_offset, 0x73c0);
assert_eq!(probe.packed_profile_len, 0x108);
assert_eq!(
probe.packed_profile_block.map_path.as_deref(),
Some("test-105.gmp")
);
assert_eq!(
probe.packed_profile_block.display_name.as_deref(),
Some("Test 105")
);
assert_eq!(probe.packed_profile_block.profile_byte_0x77, 0x07);
assert_eq!(probe.packed_profile_block.profile_byte_0x82, 0x4d);
assert_eq!(probe.packed_profile_block.profile_byte_0x97, 0x00);
assert_eq!(probe.packed_profile_block.profile_byte_0xc5, 0x00);
}
#[test]
fn builds_classic_save_load_summary() {
let summary = build_save_load_summary(
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-classic-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
None,
None,
Some(&SmpClassicRehydrateProfileProbe {
profile_family: "rt3-classic-save-container-v1".to_string(),
progress_32dc_offset: 0x76e8,
progress_3714_offset: 0x76ec,
progress_3715_offset: 0x77f8,
packed_profile_offset: 0x76f0,
packed_profile_len: 0x108,
packed_profile_len_hex: "0x108".to_string(),
packed_profile_block: SmpClassicPackedProfileBlock {
relative_len: 0x108,
relative_len_hex: "0x108".to_string(),
leading_word_0: 3,
leading_word_0_hex: "0x00000003".to_string(),
trailing_zero_word_count_after_leading_word: 3,
map_path_offset: 0x13,
map_path: Some("British Isles.gmp".to_string()),
display_name_offset: 0x46,
display_name: Some("British Isles".to_string()),
profile_byte_0x77: 0,
profile_byte_0x77_hex: "0x00".to_string(),
profile_byte_0x82: 0,
profile_byte_0x82_hex: "0x00".to_string(),
profile_byte_0x97: 0,
profile_byte_0x97_hex: "0x00".to_string(),
profile_byte_0xc5: 0,
profile_byte_0xc5_hex: "0x00".to_string(),
stable_nonzero_words: vec![],
},
ascii_runs: vec![],
}),
None,
None,
)
.expect("classic summary");
assert_eq!(summary.mechanism_family, "classic-save-rehydrate-v1");
assert_eq!(summary.mechanism_confidence, "grounded");
assert_eq!(summary.map_path.as_deref(), Some("British Isles.gmp"));
assert_eq!(summary.packed_profile_len, Some(0x108));
}
#[test]
fn builds_rt3_105_save_load_summary_with_candidate_table() {
let summary = build_save_load_summary(
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
None,
Some(&SmpRt3105PostSpanBridgeProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
bridge_family: "rt3-105-save-post-span-bridge-v1".to_string(),
bridge_evidence: vec![],
span_target_offset: 0x3678,
next_candidate_offset: Some(0x4f14),
next_candidate_delta_from_span_target: Some(0x189c),
packed_profile_offset: 0x73c0,
packed_profile_delta_from_span_target: 0x3d48,
next_candidate_delta_from_packed_profile: Some(-0x24ac),
selector_high_u16: 0x7110,
selector_high_hex: "0x7110".to_string(),
descriptor_high_u16: 0x7801,
descriptor_high_hex: "0x7801".to_string(),
next_candidate_high_u16_words: vec![0x6200, 0x0000, 0xfff7, 0x5515],
next_candidate_high_hex_words: vec![],
}),
None,
Some(&SmpRt3105PackedProfileProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
packed_profile_offset: 0x73c0,
packed_profile_len: 0x108,
packed_profile_len_hex: "0x108".to_string(),
packed_profile_block: SmpRt3105PackedProfileBlock {
relative_len: 0x108,
relative_len_hex: "0x108".to_string(),
leading_word_0: 3,
leading_word_0_hex: "0x00000003".to_string(),
trailing_zero_word_count_after_leading_word: 2,
header_flag_word_3: 1,
header_flag_word_3_hex: "0x00000001".to_string(),
map_path_offset: 0x10,
map_path: Some("Alternate USA.gmp".to_string()),
display_name_offset: 0x43,
display_name: Some("Alternate USA".to_string()),
profile_byte_0x77: 0x07,
profile_byte_0x77_hex: "0x07".to_string(),
profile_byte_0x82: 0x4d,
profile_byte_0x82_hex: "0x4d".to_string(),
profile_byte_0x97: 0,
profile_byte_0x97_hex: "0x00".to_string(),
profile_byte_0xc5: 0,
profile_byte_0xc5_hex: "0x00".to_string(),
stable_nonzero_words: vec![],
},
ascii_runs: vec![],
}),
Some(&SmpRt3105SaveNameTableProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-bridge-secondary-block".to_string(),
semantic_family: "scenario-named-candidate-availability-table".to_string(),
semantic_alignment: vec![],
header_offset: 0x6a70,
header_word_0: 0,
header_word_0_hex: "0x00000000".to_string(),
header_word_1: 0,
header_word_1_hex: "0x00000000".to_string(),
header_word_2: 0x332e,
header_word_2_hex: "0x0000332e".to_string(),
entry_stride: 0x22,
entry_stride_hex: "0x22".to_string(),
header_prefix_word_count: 11,
observed_entry_capacity: 0x44,
observed_entry_count: 67,
zero_trailer_entry_count: 3,
nonzero_trailer_entry_count: 64,
distinct_trailer_words: vec![0, 1],
distinct_trailer_hex_words: vec![
"0x00000000".to_string(),
"0x00000001".to_string(),
],
zero_trailer_entry_names: vec![
"Nuclear Power Plant".to_string(),
"Recycling Plant".to_string(),
"Uranium Mine".to_string(),
],
entries_offset: 0x6ad1,
entries_end_offset: 0x73b7,
trailing_footer_hex: "dc3200001437000000".to_string(),
footer_progress_word_0: 0x32dc,
footer_progress_word_0_hex: "0x000032dc".to_string(),
footer_progress_word_1: 0x3714,
footer_progress_word_1_hex: "0x00003714".to_string(),
footer_trailing_byte: 0,
footer_trailing_byte_hex: "0x00".to_string(),
footer_grounded_alignments: vec![],
entries: vec![],
evidence: vec![],
}),
)
.expect("1.05 summary");
assert_eq!(summary.mechanism_family, "rt3-105-save-post-span-bridge-v1");
assert_eq!(summary.mechanism_confidence, "mixed");
assert_eq!(summary.map_path.as_deref(), Some("Alternate USA.gmp"));
assert_eq!(
summary
.candidate_table
.as_ref()
.expect("candidate table")
.zero_availability_count,
3
);
}
#[test]
fn loads_classic_save_slice_from_report() {
let mut report = inspect_smp_bytes(&[]);
let classic_probe = SmpClassicRehydrateProfileProbe {
profile_family: "rt3-classic-save-container-v1".to_string(),
progress_32dc_offset: 0x76e8,
progress_3714_offset: 0x76ec,
progress_3715_offset: 0x77f8,
packed_profile_offset: 0x76f0,
packed_profile_len: 0x108,
packed_profile_len_hex: "0x108".to_string(),
packed_profile_block: SmpClassicPackedProfileBlock {
relative_len: 0x108,
relative_len_hex: "0x108".to_string(),
leading_word_0: 3,
leading_word_0_hex: "0x00000003".to_string(),
trailing_zero_word_count_after_leading_word: 3,
map_path_offset: 0x13,
map_path: Some("British Isles.gmp".to_string()),
display_name_offset: 0x46,
display_name: Some("British Isles".to_string()),
profile_byte_0x77: 0,
profile_byte_0x77_hex: "0x00".to_string(),
profile_byte_0x82: 0,
profile_byte_0x82_hex: "0x00".to_string(),
profile_byte_0x97: 0,
profile_byte_0x97_hex: "0x00".to_string(),
profile_byte_0xc5: 0,
profile_byte_0xc5_hex: "0x00".to_string(),
stable_nonzero_words: vec![],
},
ascii_runs: vec![],
};
report.classic_rehydrate_profile_probe = Some(classic_probe.clone());
report.save_load_summary = build_save_load_summary(
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-classic-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
None,
None,
Some(&classic_probe),
None,
None,
);
let slice = load_save_slice_from_report(&report).expect("classic save slice");
assert_eq!(slice.mechanism_family, "classic-save-rehydrate-v1");
assert_eq!(
slice
.profile
.as_ref()
.and_then(|profile| profile.map_path.as_deref()),
Some("British Isles.gmp")
);
assert!(slice.candidate_availability_table.is_none());
}
#[test]
fn parses_event_runtime_collection_summary_from_synthetic_chunks() {
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, 5, 3, 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(&[0x14, 0x00]);
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
bytes.extend_from_slice(&[0x11, 0x22, 0x33, 0x44]);
bytes.extend_from_slice(&[0x55, 0x66, 0x77, 0x88]);
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
bytes.extend_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
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.packed_state_version, 0x3e9);
assert_eq!(summary.live_id_bound, 5);
assert_eq!(summary.live_record_count, 3);
assert_eq!(summary.live_entry_ids, vec![1, 3, 5]);
assert_eq!(summary.records_tag_offset, 96);
assert_eq!(summary.decoded_record_count, 0);
assert_eq!(summary.records.len(), 3);
assert_eq!(summary.records[0].decode_status, "unsupported_framing");
}
fn encode_len_prefixed_string(text: &str) -> Vec<u8> {
let mut bytes = Vec::with_capacity(1 + text.len());
bytes.push(text.len() as u8);
bytes.extend_from_slice(text.as_bytes());
bytes
}
fn encode_template(
record_id: u32,
trigger_kind: u8,
flags: u8,
actions: &[Vec<u8>],
) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(PACKED_EVENT_RECORD_TEMPLATE_SYNTHETIC_MAGIC);
bytes.extend_from_slice(&record_id.to_le_bytes());
bytes.push(trigger_kind);
bytes.push(flags);
bytes.push(actions.len() as u8);
bytes.push(0);
for action in actions {
bytes.extend_from_slice(action);
}
bytes
}
fn encode_action_set_world_flag(key: &str, value: bool) -> Vec<u8> {
let mut bytes = vec![0x01];
bytes.extend_from_slice(&encode_len_prefixed_string(key));
bytes.push(u8::from(value));
bytes
}
fn encode_action_set_special_condition(label: &str, value: u32) -> Vec<u8> {
let mut bytes = vec![0x05];
bytes.extend_from_slice(&encode_len_prefixed_string(label));
bytes.extend_from_slice(&value.to_le_bytes());
bytes
}
fn encode_action_adjust_company_cash_ids(ids: &[u32], delta: i64) -> Vec<u8> {
let mut bytes = vec![0x02, 0x01, ids.len() as u8];
for id in ids {
bytes.extend_from_slice(&id.to_le_bytes());
}
bytes.extend_from_slice(&delta.to_le_bytes());
bytes
}
fn encode_action_append_template(template: Vec<u8>) -> Vec<u8> {
let mut bytes = vec![0x06];
bytes.extend_from_slice(&(template.len() as u32).to_le_bytes());
bytes.extend_from_slice(&template);
bytes
}
fn build_synthetic_event_record(
trigger_kind: u8,
flags: u8,
standalone_count: u8,
grouped_counts: [u8; 4],
text_bands: [&[u8]; 6],
actions: &[Vec<u8>],
) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(PACKED_EVENT_RECORD_SYNTHETIC_MAGIC);
bytes.push(trigger_kind);
bytes.push(flags);
bytes.push(standalone_count);
bytes.push(actions.len() as u8);
bytes.extend_from_slice(&grouped_counts);
for band in text_bands {
bytes.extend_from_slice(&(band.len() as u16).to_le_bytes());
bytes.extend_from_slice(band);
}
for action in actions {
bytes.extend_from_slice(action);
}
bytes
}
fn encode_real_optional_string(text: &str) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&(text.len() as u16).to_le_bytes());
bytes.extend_from_slice(text.as_bytes());
bytes
}
fn build_real_condition_row(
raw_condition_id: i32,
subtype: u8,
flag_seed: u8,
candidate_name: Option<&str>,
) -> Vec<u8> {
let mut bytes = Vec::new();
bytes.extend_from_slice(&(raw_condition_id as u32).to_le_bytes());
bytes.push(subtype);
while bytes.len() < PACKED_EVENT_REAL_CONDITION_ROW_LEN {
bytes.push(flag_seed.wrapping_add(bytes.len() as u8));
}
match candidate_name {
Some(text) => bytes.extend_from_slice(&encode_real_optional_string(text)),
None => bytes.extend_from_slice(&0u16.to_le_bytes()),
}
bytes
}
fn build_real_condition_row_with_threshold(
raw_condition_id: i32,
subtype: u8,
threshold: i32,
candidate_name: Option<&str>,
) -> Vec<u8> {
let mut bytes = build_real_condition_row(raw_condition_id, subtype, 0, candidate_name);
bytes[5..9].copy_from_slice(&threshold.to_le_bytes());
bytes
}
struct RealGroupedEffectRowSpec<'a> {
descriptor_id: u32,
opcode: u8,
raw_scalar_value: i32,
value_byte_0x09: u8,
value_dword_0x0d: u32,
value_byte_0x11: u8,
value_byte_0x12: u8,
value_word_0x14: u16,
value_word_0x16: u16,
locomotive_name: Option<&'a str>,
}
fn build_real_grouped_effect_row(spec: RealGroupedEffectRowSpec<'_>) -> Vec<u8> {
let mut bytes = vec![0; PACKED_EVENT_REAL_GROUPED_EFFECT_ROW_LEN];
bytes[0..4].copy_from_slice(&spec.descriptor_id.to_le_bytes());
bytes[4..8].copy_from_slice(&(spec.raw_scalar_value as u32).to_le_bytes());
bytes[8] = spec.opcode;
bytes[9] = spec.value_byte_0x09;
bytes[0x0d..0x11].copy_from_slice(&spec.value_dword_0x0d.to_le_bytes());
bytes[0x11] = spec.value_byte_0x11;
bytes[0x12] = spec.value_byte_0x12;
bytes[0x14..0x16].copy_from_slice(&spec.value_word_0x14.to_le_bytes());
bytes[0x16..0x18].copy_from_slice(&spec.value_word_0x16.to_le_bytes());
match spec.locomotive_name {
Some(text) => bytes.extend_from_slice(&encode_real_optional_string(text)),
None => bytes.extend_from_slice(&0u16.to_le_bytes()),
}
bytes
}
#[derive(Clone, Copy)]
struct RealCompactControlSpec {
mode_byte_0x7ef: u8,
primary_selector_0x7f0: u32,
grouped_mode_0x7f4: u8,
one_shot_header_0x7f5: u32,
modifier_flag_0x7f9: u8,
modifier_flag_0x7fa: u8,
grouped_target_scope_ordinals_0x7fb: [u8; PACKED_EVENT_REAL_GROUP_COUNT],
grouped_scope_checkboxes_0x7ff: [u8; PACKED_EVENT_REAL_GROUP_COUNT],
summary_toggle_0x800: u8,
grouped_territory_selectors_0x80f: [i32; PACKED_EVENT_REAL_GROUP_COUNT],
}
fn build_real_compact_control(spec: RealCompactControlSpec) -> Vec<u8> {
let mut bytes = Vec::with_capacity(PACKED_EVENT_REAL_COMPACT_CONTROL_LEN);
bytes.push(spec.mode_byte_0x7ef);
bytes.extend_from_slice(&spec.primary_selector_0x7f0.to_le_bytes());
bytes.push(spec.grouped_mode_0x7f4);
bytes.extend_from_slice(&spec.one_shot_header_0x7f5.to_le_bytes());
bytes.push(spec.modifier_flag_0x7f9);
bytes.push(spec.modifier_flag_0x7fa);
bytes.extend_from_slice(&spec.grouped_target_scope_ordinals_0x7fb);
bytes.extend_from_slice(&spec.grouped_scope_checkboxes_0x7ff);
bytes.push(spec.summary_toggle_0x800);
for selector in spec.grouped_territory_selectors_0x80f {
bytes.extend_from_slice(&selector.to_le_bytes());
}
bytes
}
fn build_real_event_record(
text_bands: [&[u8]; 6],
compact_control: Option<RealCompactControlSpec>,
condition_rows: &[Vec<u8>],
grouped_rows: [&[Vec<u8>]; 4],
) -> Vec<u8> {
let mut bytes = Vec::new();
for band in text_bands {
bytes.extend_from_slice(&(band.len() as u16).to_le_bytes());
bytes.extend_from_slice(band);
}
if let Some(spec) = compact_control {
bytes.extend_from_slice(&build_real_compact_control(spec));
}
bytes.extend_from_slice(&PACKED_EVENT_REAL_CONDITION_MARKER.to_le_bytes());
bytes.extend_from_slice(&(condition_rows.len() as u16).to_le_bytes());
for row in condition_rows {
bytes.extend_from_slice(row);
}
bytes.extend_from_slice(&PACKED_EVENT_REAL_GROUPED_EFFECT_MARKER.to_le_bytes());
for rows in grouped_rows {
bytes.extend_from_slice(&(rows.len() as u16).to_le_bytes());
}
for rows in grouped_rows {
for row in rows {
bytes.extend_from_slice(row);
}
}
bytes
}
#[test]
fn parses_synthetic_event_runtime_record_summaries_and_actions() {
let append_template = encode_template(
99,
0x0a,
0x01,
&[encode_action_set_special_condition("Imported Follow-On", 1)],
);
let record_body = build_synthetic_event_record(
7,
0x03,
1,
[0, 1, 0, 0],
[b"Alpha", b"", b"", b"", b"", b""],
&[
encode_action_set_world_flag("from_packed_root", true),
encode_action_append_template(append_template),
],
);
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(PACKED_EVENT_RECORDS_SYNTHETIC_MAGIC);
bytes.extend_from_slice(&(record_body.len() as u32).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.decoded_record_count, 1);
assert_eq!(summary.imported_runtime_record_count, 1);
assert_eq!(summary.records.len(), 1);
assert_eq!(summary.records[0].decode_status, "executable");
assert_eq!(summary.records[0].payload_family, "synthetic_harness");
assert_eq!(summary.records[0].text_bands[0].preview, "Alpha");
assert_eq!(summary.records[0].standalone_condition_row_count, 1);
assert_eq!(
summary.records[0].grouped_effect_row_counts,
vec![0, 1, 0, 0]
);
assert_eq!(summary.records[0].decoded_actions.len(), 2);
match &summary.records[0].decoded_actions[1] {
RuntimeEffect::AppendEventRecord { record } => {
assert_eq!(record.record_id, 99);
assert_eq!(record.trigger_kind, 0x0a);
}
other => panic!("unexpected decoded action: {other:?}"),
}
}
#[test]
fn decodes_company_targeted_synthetic_records_as_parity_only() {
let record_body = build_synthetic_event_record(
8,
0x01,
0,
[0, 0, 0, 0],
[b"", b"", b"", b"", b"", b""],
&[encode_action_adjust_company_cash_ids(&[7], 25)],
);
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(PACKED_EVENT_RECORDS_SYNTHETIC_MAGIC);
bytes.extend_from_slice(&(record_body.len() as u32).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.decoded_record_count, 1);
assert_eq!(summary.imported_runtime_record_count, 1);
assert_eq!(summary.records[0].decode_status, "executable");
assert_eq!(summary.records[0].payload_family, "synthetic_harness");
assert!(summary.records[0].executable_import_ready);
}
#[test]
fn parses_real_style_event_runtime_record_with_zero_rows() {
let record_body = build_real_event_record(
[b"Alpha", b"", b"", b"", b"", b""],
Some(RealCompactControlSpec {
mode_byte_0x7ef: 7,
primary_selector_0x7f0: 0x63,
grouped_mode_0x7f4: 2,
one_shot_header_0x7f5: 1,
modifier_flag_0x7f9: 1,
modifier_flag_0x7fa: 0,
grouped_target_scope_ordinals_0x7fb: [0, 1, 2, 3],
grouped_scope_checkboxes_0x7ff: [1, 0, 1, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, 10, -1, 22],
}),
&[],
[&[], &[], &[], &[]],
);
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.decoded_record_count, 1);
assert_eq!(summary.imported_runtime_record_count, 0);
assert_eq!(summary.records[0].decode_status, "parity_only");
assert_eq!(summary.records[0].payload_family, "real_packed_v1");
assert_eq!(summary.records[0].trigger_kind, Some(7));
assert_eq!(summary.records[0].one_shot, Some(true));
assert_eq!(
summary.records[0]
.compact_control
.as_ref()
.expect("real compact control should parse")
.primary_selector_0x7f0,
0x63
);
assert_eq!(summary.records[0].text_bands[0].preview, "Alpha");
assert_eq!(summary.records[0].standalone_condition_row_count, 0);
assert_eq!(summary.records[0].standalone_condition_rows.len(), 0);
assert!(summary.records[0].negative_sentinel_scope.is_none());
assert_eq!(
summary.records[0].grouped_effect_row_counts,
vec![0, 0, 0, 0]
);
assert_eq!(summary.records[0].grouped_effect_rows.len(), 0);
}
#[test]
fn parses_real_style_rows_and_side_strings() {
let condition_row = build_real_condition_row(-1, 4, 0x30, Some("AutoPlant"));
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 2,
opcode: 8,
raw_scalar_value: 7,
value_byte_0x09: 1,
value_dword_0x0d: 12,
value_byte_0x11: 2,
value_byte_0x12: 3,
value_word_0x14: 24,
value_word_0x16: 36,
locomotive_name: Some("Mikado"),
});
let group0_rows = vec![grouped_row];
let record_body = build_real_event_record(
[b"Gamma", b"", b"", b"", b"", b""],
Some(RealCompactControlSpec {
mode_byte_0x7ef: 6,
primary_selector_0x7f0: 0x2a,
grouped_mode_0x7f4: 1,
one_shot_header_0x7f5: 0,
modifier_flag_0x7f9: 2,
modifier_flag_0x7fa: 3,
grouped_target_scope_ordinals_0x7fb: [1, 4, 7, 8],
grouped_scope_checkboxes_0x7ff: [0, 1, 0, 1],
summary_toggle_0x800: 0,
grouped_territory_selectors_0x80f: [11, -1, 33, -1],
}),
&[condition_row],
[&group0_rows, &[], &[], &[]],
);
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].standalone_condition_rows.len(), 1);
assert_eq!(
summary.records[0]
.compact_control
.as_ref()
.expect("real compact control should parse")
.grouped_target_scope_ordinals_0x7fb,
vec![1, 4, 7, 8]
);
assert_eq!(
summary.records[0].standalone_condition_rows[0].raw_condition_id,
-1
);
assert_eq!(
summary.records[0].standalone_condition_rows[0]
.candidate_name
.as_deref(),
Some("AutoPlant")
);
let negative_sentinel_scope = summary.records[0]
.negative_sentinel_scope
.as_ref()
.expect("negative-sentinel scope summary should decode");
assert_eq!(
negative_sentinel_scope.company_test_scope,
RuntimeCompanyConditionTestScope::SelectedCompanyOnly
);
assert_eq!(
negative_sentinel_scope.player_test_scope,
RuntimePlayerConditionTestScope::AiPlayersOnly
);
assert!(!negative_sentinel_scope.territory_scope_selector_is_0x63);
assert_eq!(negative_sentinel_scope.source_row_indexes, vec![0]);
assert_eq!(summary.records[0].grouped_effect_rows.len(), 1);
assert_eq!(summary.records[0].grouped_effect_rows[0].opcode, 8);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.descriptor_label
.as_deref(),
Some("Company Cash")
);
assert_eq!(
summary.records[0].grouped_effect_rows[0].target_mask_bits,
Some(0x01)
);
assert_eq!(
summary.records[0].grouped_effect_rows[0].row_shape,
"multivalue_scalar"
);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.semantic_family
.as_deref(),
Some("multivalue_scalar")
);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.semantic_preview
.as_deref(),
Some("Set Company Cash to 7 with aux [2, 3, 24, 36]")
);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.locomotive_name
.as_deref(),
Some("Mikado")
);
assert_eq!(
summary.records[0].decoded_actions,
vec![RuntimeEffect::SetCompanyCash {
target: RuntimeCompanyTarget::SelectedCompany,
value: 7,
}]
);
}
#[test]
fn decodes_real_special_condition_descriptor_from_checked_in_metadata() {
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 108,
opcode: 3,
raw_scalar_value: 1,
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 group0_rows = vec![grouped_row];
let record_body = build_real_event_record(
[b"World", 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: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[],
[&group0_rows, &[], &[], &[]],
);
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("Use Wartime Cargos")
);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.parameter_family
.as_deref(),
Some("special_condition_scalar")
);
assert_eq!(
summary.records[0].decoded_actions,
vec![RuntimeEffect::SetSpecialCondition {
label: "Use Wartime Cargos".to_string(),
value: 1,
}]
);
assert!(summary.records[0].executable_import_ready);
}
#[test]
fn decodes_real_candidate_availability_descriptor_from_checked_in_metadata() {
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 109,
opcode: 3,
raw_scalar_value: 1,
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 group0_rows = vec![grouped_row];
let record_body = build_real_event_record(
[b"World", 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: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[],
[&group0_rows, &[], &[], &[]],
);
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("Turbo Diesel Availability")
);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.parameter_family
.as_deref(),
Some("candidate_availability_scalar")
);
assert_eq!(
summary.records[0].decoded_actions,
vec![RuntimeEffect::SetCandidateAvailability {
name: "Turbo Diesel".to_string(),
value: 1,
}]
);
assert!(summary.records[0].executable_import_ready);
}
#[test]
fn decodes_real_special_condition_threshold_from_checked_in_world_condition_metadata() {
let condition_row = build_real_condition_row_with_threshold(3835, 0, 1, None);
let record_body = build_real_event_record(
[b"World", 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: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[condition_row],
[&[], &[], &[], &[]],
);
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].standalone_condition_rows[0]
.metric
.as_deref(),
Some("Special Condition: Use Wartime Cargos")
);
assert_eq!(
summary.records[0].standalone_condition_rows[0]
.semantic_family
.as_deref(),
Some("world_state_threshold")
);
assert_eq!(
summary.records[0].decoded_conditions,
vec![RuntimeCondition::SpecialConditionThreshold {
label: "Use Wartime Cargos".to_string(),
comparator: RuntimeConditionComparator::Ge,
value: 1,
}]
);
}
#[test]
fn decodes_real_candidate_availability_threshold_from_checked_in_world_condition_metadata() {
let condition_row = build_real_condition_row_with_threshold(
REAL_CANDIDATE_AVAILABILITY_CONDITION_TEMPLATE_ID,
0,
2,
Some("Mogul"),
);
let record_body = build_real_event_record(
[b"World", 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: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[condition_row],
[&[], &[], &[], &[]],
);
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].standalone_condition_rows[0]
.metric
.as_deref(),
Some("Candidate Availability: Mogul")
);
assert_eq!(
summary.records[0].decoded_conditions,
vec![RuntimeCondition::CandidateAvailabilityThreshold {
name: "Mogul".to_string(),
comparator: RuntimeConditionComparator::Ge,
value: 2,
}]
);
}
#[test]
fn decodes_real_economic_status_threshold_from_checked_in_world_condition_metadata() {
let condition_row = build_real_condition_row_with_threshold(2350, 0, 4, None);
let record_body = build_real_event_record(
[b"World", 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: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[condition_row],
[&[], &[], &[], &[]],
);
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].standalone_condition_rows[0]
.metric
.as_deref(),
Some("Economic Status")
);
assert_eq!(
summary.records[0].decoded_conditions,
vec![RuntimeCondition::EconomicStatusCodeThreshold {
comparator: RuntimeConditionComparator::Ge,
value: 4,
}]
);
}
#[test]
fn decodes_real_named_locomotive_availability_threshold_from_checked_in_metadata() {
let condition_row = build_real_condition_row_with_threshold(
REAL_NAMED_LOCOMOTIVE_AVAILABILITY_CONDITION_ID,
4,
42,
Some("Big Boy"),
);
let record_body = build_real_event_record(
[b"World", 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: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[condition_row],
[&[], &[], &[], &[]],
);
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].standalone_condition_rows[0]
.metric
.as_deref(),
Some("Named Locomotive Availability: Big Boy")
);
assert_eq!(
summary.records[0].standalone_condition_rows[0]
.semantic_family
.as_deref(),
Some("world_scalar_threshold")
);
assert_eq!(
summary.records[0].decoded_conditions,
vec![RuntimeCondition::NamedLocomotiveAvailabilityThreshold {
name: "Big Boy".to_string(),
comparator: RuntimeConditionComparator::Eq,
value: 42,
}]
);
}
#[test]
fn decodes_real_named_locomotive_cost_threshold_from_checked_in_metadata() {
let condition_row = build_real_condition_row_with_threshold(
REAL_NAMED_LOCOMOTIVE_COST_CONDITION_ID,
0,
250000,
Some("Locomotive 1"),
);
let record_body = build_real_event_record(
[b"World", 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: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[condition_row],
[&[], &[], &[], &[]],
);
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].standalone_condition_rows[0]
.metric
.as_deref(),
Some("Named Locomotive Cost: Locomotive 1")
);
assert_eq!(
summary.records[0].decoded_conditions,
vec![RuntimeCondition::NamedLocomotiveCostThreshold {
name: "Locomotive 1".to_string(),
comparator: RuntimeConditionComparator::Ge,
value: 250000,
}]
);
}
#[test]
fn decodes_real_named_cargo_production_threshold_from_checked_in_metadata() {
let condition_row = build_real_condition_row_with_threshold(
REAL_CARGO_PRODUCTION_CONDITION_TEMPLATE_ID,
4,
125,
Some("Cargo Production Slot 1"),
);
let record_body = build_real_event_record(
[b"World", 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: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[condition_row],
[&[], &[], &[], &[]],
);
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].standalone_condition_rows[0]
.metric
.as_deref(),
Some("Cargo Production: Cargo Production Slot 1")
);
assert_eq!(
summary.records[0].standalone_condition_rows[0].recovered_cargo_slot,
Some(1)
);
assert_eq!(
summary.records[0].decoded_conditions,
vec![RuntimeCondition::CargoProductionSlotThreshold {
slot: 1,
label: "Cargo Production Slot 1".to_string(),
comparator: RuntimeConditionComparator::Eq,
value: 125,
}]
);
}
#[test]
fn decodes_real_world_scalar_thresholds_from_checked_in_metadata() {
let condition_rows = vec![
build_real_condition_row_with_threshold(
REAL_CARGO_PRODUCTION_TOTAL_CONDITION_ID,
0,
200,
None,
),
build_real_condition_row_with_threshold(
REAL_FACTORY_PRODUCTION_TOTAL_CONDITION_ID,
4,
125,
None,
),
build_real_condition_row_with_threshold(
REAL_FARM_MINE_PRODUCTION_TOTAL_CONDITION_ID,
4,
75,
None,
),
build_real_condition_row_with_threshold(
REAL_OTHER_CARGO_PRODUCTION_TOTAL_CONDITION_ID,
4,
30,
None,
),
build_real_condition_row_with_threshold(
REAL_LIMITED_TRACK_BUILDING_AMOUNT_CONDITION_ID,
4,
18,
None,
),
build_real_condition_row_with_threshold(
REAL_TERRITORY_ACCESS_COST_CONDITION_ID,
4,
750000,
None,
),
];
let record_body = build_real_event_record(
[b"World", 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: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&condition_rows,
[&[], &[], &[], &[]],
);
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].standalone_condition_rows[0]
.metric
.as_deref(),
Some("Cargo Production Total")
);
assert_eq!(
summary.records[0].standalone_condition_rows[0]
.semantic_family
.as_deref(),
Some("world_scalar_threshold")
);
assert_eq!(
summary.records[0].decoded_conditions,
vec![
RuntimeCondition::CargoProductionTotalThreshold {
comparator: RuntimeConditionComparator::Ge,
value: 200,
},
RuntimeCondition::FactoryProductionTotalThreshold {
comparator: RuntimeConditionComparator::Eq,
value: 125,
},
RuntimeCondition::FarmMineProductionTotalThreshold {
comparator: RuntimeConditionComparator::Eq,
value: 75,
},
RuntimeCondition::OtherCargoProductionTotalThreshold {
comparator: RuntimeConditionComparator::Eq,
value: 30,
},
RuntimeCondition::LimitedTrackBuildingAmountThreshold {
comparator: RuntimeConditionComparator::Eq,
value: 18,
},
RuntimeCondition::TerritoryAccessCostThreshold {
comparator: RuntimeConditionComparator::Eq,
value: 750000,
},
]
);
}
#[test]
fn decodes_real_world_flag_condition_from_checked_in_world_condition_metadata() {
let condition_row = build_real_condition_row_with_threshold(2535, 4, 1, None);
let record_body = build_real_event_record(
[b"World", 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: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[condition_row],
[&[], &[], &[], &[]],
);
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].standalone_condition_rows[0]
.metric
.as_deref(),
Some("World Flag: Disable Stock Buying and Selling")
);
assert_eq!(
summary.records[0].standalone_condition_rows[0]
.semantic_family
.as_deref(),
Some("world_flag_equals")
);
assert_eq!(
summary.records[0].decoded_conditions,
vec![RuntimeCondition::WorldFlagEquals {
key: "world.disable_stock_buying_and_selling".to_string(),
value: true,
}]
);
}
#[test]
fn looks_up_checked_in_world_scalar_condition_metadata() {
let named_cargo =
real_ordinary_condition_metadata(REAL_CARGO_PRODUCTION_CONDITION_TEMPLATE_ID)
.expect("named cargo condition metadata should exist");
assert_eq!(named_cargo.label, "%1 Production");
let availability =
real_ordinary_condition_metadata(REAL_NAMED_LOCOMOTIVE_AVAILABILITY_CONDITION_ID)
.expect("availability condition metadata should exist");
assert_eq!(availability.label, "Unknown Loco Available");
let cost = real_ordinary_condition_metadata(REAL_NAMED_LOCOMOTIVE_COST_CONDITION_ID)
.expect("cost condition metadata should exist");
assert_eq!(cost.label, "Unknown Loco Cost");
let cargo = real_ordinary_condition_metadata(REAL_CARGO_PRODUCTION_TOTAL_CONDITION_ID)
.expect("cargo condition metadata should exist");
assert_eq!(cargo.label, "All Cargo Production");
let factory = real_ordinary_condition_metadata(REAL_FACTORY_PRODUCTION_TOTAL_CONDITION_ID)
.expect("factory production condition metadata should exist");
assert_eq!(factory.label, "All Factory Production");
let farm_mine =
real_ordinary_condition_metadata(REAL_FARM_MINE_PRODUCTION_TOTAL_CONDITION_ID)
.expect("farm/mine production condition metadata should exist");
assert_eq!(farm_mine.label, "All Farm/Mine Production");
let build_limit =
real_ordinary_condition_metadata(REAL_LIMITED_TRACK_BUILDING_AMOUNT_CONDITION_ID)
.expect("build-limit condition metadata should exist");
assert_eq!(build_limit.label, "Limited Track Building Amount");
let access_cost = real_ordinary_condition_metadata(REAL_TERRITORY_ACCESS_COST_CONDITION_ID)
.expect("territory-access-cost condition metadata should exist");
assert_eq!(access_cost.label, "Access Rights Cost:");
}
#[test]
fn looks_up_checked_in_chairman_and_governance_condition_metadata() {
let world_variable = real_ordinary_condition_metadata(REAL_WORLD_VARIABLE_1_CONDITION_ID)
.expect("world-variable condition metadata should exist");
assert_eq!(world_variable.label, "Game Variable 1");
let player_variable = real_ordinary_condition_metadata(REAL_PLAYER_VARIABLE_3_CONDITION_ID)
.expect("player-variable condition metadata should exist");
assert_eq!(player_variable.label, "Player Variable 3");
let chairman_cash = real_ordinary_condition_metadata(REAL_CHAIRMAN_CASH_CONDITION_ID)
.expect("chairman cash condition metadata should exist");
assert_eq!(chairman_cash.label, "Player Cash");
let holdings = real_ordinary_condition_metadata(REAL_CHAIRMAN_HOLDINGS_TOTAL_CONDITION_ID)
.expect("chairman holdings condition metadata should exist");
assert_eq!(holdings.label, "Player Stock Value");
let net_worth = real_ordinary_condition_metadata(REAL_CHAIRMAN_NET_WORTH_CONDITION_ID)
.expect("chairman net worth condition metadata should exist");
assert_eq!(net_worth.label, "Player Net Worth");
let purchasing_power =
real_ordinary_condition_metadata(REAL_CHAIRMAN_PURCHASING_POWER_CONDITION_ID)
.expect("chairman purchasing-power condition metadata should exist");
assert_eq!(purchasing_power.label, "Purchasing Power");
let investor_confidence =
real_ordinary_condition_metadata(REAL_INVESTOR_CONFIDENCE_CONDITION_ID)
.expect("investor-confidence condition metadata should exist");
assert_eq!(investor_confidence.label, "Investor Confidence");
let credit_rating = real_ordinary_condition_metadata(REAL_CREDIT_RATING_CONDITION_ID)
.expect("credit-rating condition metadata should exist");
assert_eq!(credit_rating.label, "Credit Rating");
let prime_rate = real_ordinary_condition_metadata(REAL_PRIME_RATE_CONDITION_ID)
.expect("prime-rate condition metadata should exist");
assert_eq!(prime_rate.label, "Prime Rate");
let management_attitude =
real_ordinary_condition_metadata(REAL_MANAGEMENT_ATTITUDE_CONDITION_ID)
.expect("management-attitude condition metadata should exist");
assert_eq!(management_attitude.label, "Management Attitude");
let book_value = real_ordinary_condition_metadata(REAL_BOOK_VALUE_PER_SHARE_CONDITION_ID)
.expect("book value condition metadata should exist");
assert_eq!(book_value.label, "Book Value Per Share");
}
#[test]
fn decodes_world_variable_condition() {
let row = SmpLoadedPackedEventConditionRowSummary {
row_index: 0,
raw_condition_id: REAL_WORLD_VARIABLE_1_CONDITION_ID,
subtype: 4,
flag_bytes: {
let mut bytes = vec![0; 25];
bytes[0..4].copy_from_slice(&111_i32.to_le_bytes());
bytes
},
candidate_name: None,
comparator: Some("eq".to_string()),
metric: Some("Game Variable 1".to_string()),
semantic_family: Some("numeric_threshold".to_string()),
semantic_preview: Some("Test Game Variable 1 == 111".to_string()),
recovered_cargo_slot: None,
recovered_cargo_class: None,
requires_candidate_name_binding: false,
notes: vec![],
};
assert_eq!(
decode_real_condition_row(&row, None),
Some(RuntimeCondition::WorldVariableThreshold {
index: 1,
comparator: RuntimeConditionComparator::Eq,
value: 111,
})
);
}
#[test]
fn decodes_player_variable_condition_from_selected_player_scope() {
let row = SmpLoadedPackedEventConditionRowSummary {
row_index: 0,
raw_condition_id: REAL_PLAYER_VARIABLE_3_CONDITION_ID,
subtype: 4,
flag_bytes: {
let mut bytes = vec![0; 25];
bytes[0..4].copy_from_slice(&333_i32.to_le_bytes());
bytes
},
candidate_name: None,
comparator: Some("eq".to_string()),
metric: Some("Player Variable 3".to_string()),
semantic_family: Some("numeric_threshold".to_string()),
semantic_preview: Some("Test Player Variable 3 == 333".to_string()),
recovered_cargo_slot: None,
recovered_cargo_class: None,
requires_candidate_name_binding: false,
notes: vec![],
};
let negative_scope = SmpLoadedPackedEventNegativeSentinelScopeSummary {
company_test_scope: RuntimeCompanyConditionTestScope::Disabled,
player_test_scope: RuntimePlayerConditionTestScope::SelectedPlayerOnly,
territory_scope_selector_is_0x63: false,
source_row_indexes: vec![0],
};
assert_eq!(
decode_real_condition_row(&row, Some(&negative_scope)),
Some(RuntimeCondition::PlayerVariableThreshold {
target: RuntimePlayerTarget::SelectedPlayer,
index: 3,
comparator: RuntimeConditionComparator::Eq,
value: 333,
})
);
}
#[test]
fn decodes_territory_variable_condition_with_world_territory_scope() {
let row = SmpLoadedPackedEventConditionRowSummary {
row_index: 0,
raw_condition_id: REAL_TERRITORY_VARIABLE_4_CONDITION_ID,
subtype: 4,
flag_bytes: {
let mut bytes = vec![0; 25];
bytes[0..4].copy_from_slice(&444_i32.to_le_bytes());
bytes
},
candidate_name: None,
comparator: Some("eq".to_string()),
metric: Some("Territory Variable 4".to_string()),
semantic_family: Some("numeric_threshold".to_string()),
semantic_preview: Some("Test Territory Variable 4 == 444".to_string()),
recovered_cargo_slot: None,
recovered_cargo_class: None,
requires_candidate_name_binding: false,
notes: vec![],
};
let negative_scope = SmpLoadedPackedEventNegativeSentinelScopeSummary {
company_test_scope: RuntimeCompanyConditionTestScope::Disabled,
player_test_scope: RuntimePlayerConditionTestScope::Disabled,
territory_scope_selector_is_0x63: true,
source_row_indexes: vec![0],
};
assert_eq!(
decode_real_condition_row(&row, Some(&negative_scope)),
Some(RuntimeCondition::TerritoryVariableThreshold {
target: RuntimeTerritoryTarget::AllTerritories,
index: 4,
comparator: RuntimeConditionComparator::Eq,
value: 444,
})
);
}
#[test]
fn decodes_chairman_cash_condition_from_selected_player_scope() {
let row = SmpLoadedPackedEventConditionRowSummary {
row_index: 0,
raw_condition_id: REAL_CHAIRMAN_CASH_CONDITION_ID,
subtype: 4,
flag_bytes: {
let mut bytes = vec![0; 25];
bytes[0..4].copy_from_slice(&500_i32.to_le_bytes());
bytes
},
candidate_name: None,
comparator: Some("eq".to_string()),
metric: Some("Player Cash".to_string()),
semantic_family: Some("numeric_threshold".to_string()),
semantic_preview: Some("Test Player Cash == 500".to_string()),
recovered_cargo_slot: None,
recovered_cargo_class: None,
requires_candidate_name_binding: false,
notes: vec![],
};
let negative_scope = SmpLoadedPackedEventNegativeSentinelScopeSummary {
company_test_scope: RuntimeCompanyConditionTestScope::Disabled,
player_test_scope: RuntimePlayerConditionTestScope::SelectedPlayerOnly,
territory_scope_selector_is_0x63: false,
source_row_indexes: vec![0],
};
assert_eq!(
decode_real_condition_row(&row, Some(&negative_scope)),
Some(RuntimeCondition::ChairmanNumericThreshold {
target: RuntimeChairmanTarget::SelectedChairman,
metric: RuntimeChairmanMetric::CurrentCash,
comparator: RuntimeConditionComparator::Eq,
value: 500,
})
);
}
#[test]
fn decodes_book_value_per_share_condition_to_company_metric() {
let row = SmpLoadedPackedEventConditionRowSummary {
row_index: 0,
raw_condition_id: REAL_BOOK_VALUE_PER_SHARE_CONDITION_ID,
subtype: 4,
flag_bytes: {
let mut bytes = vec![0; 25];
bytes[0..4].copy_from_slice(&2620_i32.to_le_bytes());
bytes
},
candidate_name: None,
comparator: Some("eq".to_string()),
metric: Some("Book Value Per Share".to_string()),
semantic_family: Some("numeric_threshold".to_string()),
semantic_preview: Some("Test Book Value Per Share == 2620".to_string()),
recovered_cargo_slot: None,
recovered_cargo_class: None,
requires_candidate_name_binding: false,
notes: vec![],
};
assert_eq!(
decode_real_condition_row(&row, None),
Some(RuntimeCondition::CompanyNumericThreshold {
target: RuntimeCompanyTarget::ConditionTrueCompany,
metric: RuntimeCompanyMetric::BookValuePerShare,
comparator: RuntimeConditionComparator::Eq,
value: 2620,
})
);
}
#[test]
fn decodes_investor_confidence_condition_to_company_metric() {
let row = SmpLoadedPackedEventConditionRowSummary {
row_index: 0,
raw_condition_id: REAL_INVESTOR_CONFIDENCE_CONDITION_ID,
subtype: 4,
flag_bytes: {
let mut bytes = vec![0; 25];
bytes[0..4].copy_from_slice(&37_i32.to_le_bytes());
bytes
},
candidate_name: None,
comparator: Some("eq".to_string()),
metric: Some("Investor Confidence".to_string()),
semantic_family: Some("numeric_threshold".to_string()),
semantic_preview: Some("Test Investor Confidence == 37".to_string()),
recovered_cargo_slot: None,
recovered_cargo_class: None,
requires_candidate_name_binding: false,
notes: vec![],
};
assert_eq!(
decode_real_condition_row(&row, None),
Some(RuntimeCondition::CompanyNumericThreshold {
target: RuntimeCompanyTarget::ConditionTrueCompany,
metric: RuntimeCompanyMetric::InvestorConfidence,
comparator: RuntimeConditionComparator::Eq,
value: 37,
})
);
}
#[test]
fn decodes_management_attitude_condition_to_company_metric() {
let row = SmpLoadedPackedEventConditionRowSummary {
row_index: 0,
raw_condition_id: REAL_MANAGEMENT_ATTITUDE_CONDITION_ID,
subtype: 4,
flag_bytes: {
let mut bytes = vec![0; 25];
bytes[0..4].copy_from_slice(&58_i32.to_le_bytes());
bytes
},
candidate_name: None,
comparator: Some("eq".to_string()),
metric: Some("Management Attitude".to_string()),
semantic_family: Some("numeric_threshold".to_string()),
semantic_preview: Some("Test Management Attitude == 58".to_string()),
recovered_cargo_slot: None,
recovered_cargo_class: None,
requires_candidate_name_binding: false,
notes: vec![],
};
assert_eq!(
decode_real_condition_row(&row, None),
Some(RuntimeCondition::CompanyNumericThreshold {
target: RuntimeCompanyTarget::ConditionTrueCompany,
metric: RuntimeCompanyMetric::ManagementAttitude,
comparator: RuntimeConditionComparator::Eq,
value: 58,
})
);
}
#[test]
fn looks_up_checked_in_world_flag_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(110).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Disable Stock Buying and Selling");
assert_eq!(metadata.parameter_family, "world_flag_toggle");
assert_eq!(
metadata.runtime_key,
Some("world.disable_stock_buying_and_selling")
);
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_descriptors_from_checked_in_effect_table() {
let stock_prices =
real_grouped_effect_descriptor_metadata(55).expect("descriptor metadata should exist");
assert_eq!(stock_prices.label, "Stock Prices");
assert_eq!(
stock_prices.parameter_family,
"company_finance_shell_scalar"
);
assert_eq!(
real_grouped_effect_runtime_status_name(stock_prices.runtime_status),
"shell_owned"
);
assert!(!stock_prices.executable_in_runtime);
let merger_premium =
real_grouped_effect_descriptor_metadata(58).expect("descriptor metadata should exist");
assert_eq!(merger_premium.label, "Merger Premium");
assert_eq!(
merger_premium.parameter_family,
"company_finance_shell_scalar"
);
assert_eq!(
real_grouped_effect_runtime_status_name(merger_premium.runtime_status),
"shell_owned"
);
assert!(!merger_premium.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 =
real_grouped_effect_descriptor_metadata(111).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Disable Margin Buying/Short Selling Stock");
assert_eq!(metadata.parameter_family, "world_flag_toggle");
assert_eq!(
runtime_world_flag_key(metadata),
Some("world.disable_margin_buying_short_selling_stock".to_string())
);
assert!(metadata.executable_in_runtime);
}
#[test]
fn looks_up_limited_track_building_amount_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(122).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Limited Track Building Amount");
assert_eq!(metadata.parameter_family, "world_track_build_limit_scalar");
assert_eq!(metadata.runtime_key, None);
assert!(metadata.executable_in_runtime);
}
#[test]
fn looks_up_recovered_late_world_toggle_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(143).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Disable Train Crashes AND Breakdowns");
assert_eq!(metadata.parameter_family, "world_flag_toggle");
assert_eq!(
runtime_world_flag_key(metadata),
Some("world.disable_train_crashes_and_breakdowns".to_string())
);
assert!(metadata.executable_in_runtime);
}
#[test]
fn looks_up_recovered_locomotive_availability_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(250).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Big Boy 4-8-8-4 Availability");
assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "locomotive_availability_scalar");
assert_eq!(metadata.runtime_key, None);
assert!(metadata.executable_in_runtime);
}
#[test]
fn looks_up_upper_band_recovered_locomotive_availability_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(457).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Upper-Band Locomotive Availability Slot 1");
assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "locomotive_availability_scalar");
assert_eq!(recovered_locomotive_availability_loco_id(457), None);
}
#[test]
fn looks_up_extended_lower_band_locomotive_availability_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(301).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Zephyr Availability");
assert_eq!(metadata.parameter_family, "locomotive_availability_scalar");
assert_eq!(recovered_locomotive_availability_loco_id(301), Some(61));
assert!(metadata.executable_in_runtime);
}
#[test]
fn parses_recovered_locomotive_availability_row_with_structured_locomotive_id() {
let row_bytes = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 250,
raw_scalar_value: 1,
opcode: 3,
value_byte_0x09: 0,
value_dword_0x0d: 0,
value_byte_0x11: 0,
value_byte_0x12: 0,
value_word_0x14: 0,
value_word_0x16: 0,
locomotive_name: None,
});
let row = parse_real_grouped_effect_row_summary(&row_bytes, 0, 0, None)
.expect("row should parse");
assert_eq!(row.descriptor_id, 250);
assert_eq!(
row.descriptor_label.as_deref(),
Some("Big Boy 4-8-8-4 Availability")
);
assert_eq!(row.recovered_locomotive_id, Some(10));
assert_eq!(
row.parameter_family.as_deref(),
Some("locomotive_availability_scalar")
);
}
#[test]
fn looks_up_recovered_cargo_production_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(230).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Cargo Production Slot 1");
assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "cargo_production_scalar");
assert_eq!(metadata.runtime_key, None);
assert!(metadata.executable_in_runtime);
}
#[test]
fn looks_up_recovered_all_cargo_price_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(105).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "All Cargo Prices");
assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "cargo_price_scalar");
assert_eq!(metadata.runtime_key, None);
assert!(metadata.executable_in_runtime);
}
#[test]
fn looks_up_named_cargo_price_slot_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(106).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Alcohol Price");
assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "cargo_price_scalar");
assert_eq!(metadata.runtime_key, None);
assert!(metadata.executable_in_runtime);
}
#[test]
fn looks_up_runtime_variable_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(43).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Company Variable 1");
assert_eq!(metadata.target_mask_bits, 0x01);
assert_eq!(metadata.parameter_family, "runtime_variable_scalar");
assert!(metadata.executable_in_runtime);
}
#[test]
fn looks_up_recovered_aggregate_cargo_production_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(177).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "All Cargo Production");
assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "cargo_production_scalar");
assert_eq!(metadata.runtime_key, None);
assert!(metadata.executable_in_runtime);
}
#[test]
fn looks_up_grounded_named_cargo_production_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(180).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Alcohol Production");
assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "cargo_production_scalar");
assert_eq!(metadata.runtime_key, None);
assert!(metadata.executable_in_runtime);
}
#[test]
fn looks_up_recovered_lower_band_locomotive_cost_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(352).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "2-D-2 Cost");
assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "locomotive_cost_scalar");
assert_eq!(metadata.runtime_key, None);
assert_eq!(recovered_locomotive_cost_loco_id(352), Some(1));
assert!(metadata.executable_in_runtime);
}
#[test]
fn looks_up_recovered_upper_band_locomotive_cost_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(475).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Upper-Band Locomotive Cost Slot 1");
assert_eq!(metadata.parameter_family, "locomotive_cost_scalar");
assert_eq!(recovered_locomotive_cost_loco_id(475), None);
assert!(!metadata.executable_in_runtime);
}
#[test]
fn looks_up_extended_lower_band_locomotive_cost_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(412).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Zephyr Cost");
assert_eq!(metadata.parameter_family, "locomotive_cost_scalar");
assert_eq!(recovered_locomotive_cost_loco_id(412), Some(61));
assert!(metadata.executable_in_runtime);
}
#[test]
fn looks_up_recovered_territory_access_cost_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(453).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Territory Access Cost");
assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "territory_access_cost_scalar");
assert_eq!(metadata.runtime_key, None);
assert!(metadata.executable_in_runtime);
}
#[test]
fn parses_recovered_locomotive_cost_row_with_structured_locomotive_id() {
let row_bytes = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 352,
raw_scalar_value: 250000,
opcode: 3,
value_byte_0x09: 0,
value_dword_0x0d: 0,
value_byte_0x11: 0,
value_byte_0x12: 0,
value_word_0x14: 0,
value_word_0x16: 0,
locomotive_name: None,
});
let row = parse_real_grouped_effect_row_summary(&row_bytes, 0, 0, None)
.expect("row should parse");
assert_eq!(row.descriptor_id, 352);
assert_eq!(row.descriptor_label.as_deref(), Some("2-D-2 Cost"));
assert_eq!(row.recovered_locomotive_id, Some(1));
assert_eq!(
row.parameter_family.as_deref(),
Some("locomotive_cost_scalar")
);
}
#[test]
fn parses_grounded_named_cargo_production_row_with_label() {
let row_bytes = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 180,
raw_scalar_value: 160,
opcode: 3,
value_byte_0x09: 0,
value_dword_0x0d: 0,
value_byte_0x11: 0,
value_byte_0x12: 0,
value_word_0x14: 0,
value_word_0x16: 0,
locomotive_name: None,
});
let row = parse_real_grouped_effect_row_summary(&row_bytes, 0, 0, None)
.expect("row should parse");
assert_eq!(row.descriptor_id, 180);
assert_eq!(row.descriptor_label.as_deref(), Some("Alcohol Production"));
assert_eq!(row.recovered_cargo_label.as_deref(), Some("Alcohol"));
assert_eq!(
row.parameter_family.as_deref(),
Some("cargo_production_scalar")
);
}
#[test]
fn parses_grounded_named_cargo_price_row_with_label() {
let row_bytes = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 106,
raw_scalar_value: 140,
opcode: 3,
value_byte_0x09: 0,
value_dword_0x0d: 0,
value_byte_0x11: 0,
value_byte_0x12: 0,
value_word_0x14: 0,
value_word_0x16: 0,
locomotive_name: None,
});
let row = parse_real_grouped_effect_row_summary(&row_bytes, 0, 0, None)
.expect("row should parse");
assert_eq!(row.descriptor_id, 106);
assert_eq!(row.descriptor_label.as_deref(), Some("Alcohol Price"));
assert_eq!(row.recovered_cargo_label.as_deref(), Some("Alcohol"));
assert_eq!(row.parameter_family.as_deref(), Some("cargo_price_scalar"));
}
#[test]
fn looks_up_recovered_locomotive_policy_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(454).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "All Steam Locos Avail.");
assert_eq!(metadata.parameter_family, "world_flag_toggle");
assert_eq!(
metadata.runtime_key,
Some("world.all_steam_locos_available")
);
assert!(metadata.executable_in_runtime);
}
#[test]
fn decodes_recovered_locomotive_policy_descriptor_from_checked_in_metadata() {
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 454,
opcode: 0,
raw_scalar_value: 1,
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 group0_rows = vec![grouped_row];
let record_body = build_real_event_record(
[b"Locos", b"", b"", b"", b"", b""],
Some(RealCompactControlSpec {
mode_byte_0x7ef: 6,
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: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[],
[&group0_rows, &[], &[], &[]],
);
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("All Steam Locos Avail.")
);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.parameter_family
.as_deref(),
Some("world_flag_toggle")
);
assert_eq!(
summary.records[0].decoded_actions,
vec![RuntimeEffect::SetWorldFlag {
key: "world.all_steam_locos_available".to_string(),
value: true,
}]
);
assert!(summary.records[0].executable_import_ready);
}
#[test]
fn looks_up_checked_in_deactivate_player_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(14).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Deactivate Player");
assert_eq!(metadata.parameter_family, "player_lifecycle_toggle");
assert_eq!(metadata.runtime_key, None);
assert!(metadata.executable_in_runtime);
}
#[test]
fn decodes_real_deactivate_player_descriptor_from_checked_in_metadata() {
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 14,
opcode: 1,
raw_scalar_value: 1,
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 group0_rows = vec![grouped_row];
let record_body = build_real_event_record(
[b"Players", 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],
}),
&[],
[&group0_rows, &[], &[], &[]],
);
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("Deactivate Player")
);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.parameter_family
.as_deref(),
Some("player_lifecycle_toggle")
);
assert_eq!(
summary.records[0].decoded_actions,
vec![RuntimeEffect::DeactivatePlayer {
target: RuntimePlayerTarget::SelectedPlayer,
}]
);
assert!(summary.records[0].executable_import_ready);
}
#[test]
fn decodes_real_world_flag_descriptor_with_checked_in_runtime_key() {
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 110,
opcode: 0,
raw_scalar_value: 1,
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 group0_rows = vec![grouped_row];
let record_body = build_real_event_record(
[b"World", 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: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[],
[&group0_rows, &[], &[], &[]],
);
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("Disable Stock Buying and Selling")
);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.parameter_family
.as_deref(),
Some("world_flag_toggle")
);
assert_eq!(
summary.records[0].decoded_actions,
vec![RuntimeEffect::SetWorldFlag {
key: "world.disable_stock_buying_and_selling".to_string(),
value: true,
}]
);
assert!(summary.records[0].executable_import_ready);
}
#[test]
fn decodes_recovered_world_toggle_descriptor_family() {
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 131,
opcode: 0,
raw_scalar_value: 1,
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 group0_rows = vec![grouped_row];
let record_body = build_real_event_record(
[b"World", 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: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[],
[&group0_rows, &[], &[], &[]],
);
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("Disable Starting Any Companies")
);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.parameter_family
.as_deref(),
Some("world_flag_toggle")
);
assert_eq!(
summary.records[0].decoded_actions,
vec![RuntimeEffect::SetWorldFlag {
key: "world.disable_starting_any_companies".to_string(),
value: true,
}]
);
assert!(summary.records[0].executable_import_ready);
}
#[test]
fn decodes_limited_track_building_amount_descriptor_family() {
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 122,
opcode: 3,
raw_scalar_value: 18,
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 group0_rows = vec![grouped_row];
let record_body = build_real_event_record(
[b"World", b"", b"", b"", b"", b""],
Some(RealCompactControlSpec {
mode_byte_0x7ef: 6,
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: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[],
[&group0_rows, &[], &[], &[]],
);
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("Limited Track Building Amount")
);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.parameter_family
.as_deref(),
Some("world_track_build_limit_scalar")
);
assert_eq!(
summary.records[0].decoded_actions,
vec![RuntimeEffect::SetLimitedTrackBuildingAmount { value: 18 }]
);
assert!(summary.records[0].executable_import_ready);
}
#[test]
fn decodes_recovered_late_world_toggle_descriptor_family() {
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 144,
opcode: 0,
raw_scalar_value: 1,
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 group0_rows = vec![grouped_row];
let record_body = build_real_event_record(
[b"World", 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: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[],
[&group0_rows, &[], &[], &[]],
);
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("AI Ignore Territories At Startup")
);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.parameter_family
.as_deref(),
Some("world_flag_toggle")
);
assert_eq!(
summary.records[0].decoded_actions,
vec![RuntimeEffect::SetWorldFlag {
key: "world.ai_ignore_territories_at_startup".to_string(),
value: true,
}]
);
assert!(summary.records[0].executable_import_ready);
}
#[test]
fn decodes_negative_sentinel_scope_modifiers_and_territory_marker() {
for (value, expected) in [
(0, RuntimeCompanyConditionTestScope::Disabled),
(1, RuntimeCompanyConditionTestScope::AllCompanies),
(2, RuntimeCompanyConditionTestScope::SelectedCompanyOnly),
(3, RuntimeCompanyConditionTestScope::AiCompaniesOnly),
(4, RuntimeCompanyConditionTestScope::HumanCompaniesOnly),
] {
assert_eq!(decode_company_condition_test_scope(value), Some(expected));
}
for (value, expected) in [
(0, RuntimePlayerConditionTestScope::Disabled),
(1, RuntimePlayerConditionTestScope::AllPlayers),
(2, RuntimePlayerConditionTestScope::SelectedPlayerOnly),
(3, RuntimePlayerConditionTestScope::AiPlayersOnly),
(4, RuntimePlayerConditionTestScope::HumanPlayersOnly),
] {
assert_eq!(decode_player_condition_test_scope(value), Some(expected));
}
let rows = vec![SmpLoadedPackedEventConditionRowSummary {
row_index: 0,
raw_condition_id: -1,
subtype: 4,
flag_bytes: vec![0x30; 25],
candidate_name: Some("AutoPlant".to_string()),
comparator: None,
metric: None,
semantic_family: None,
semantic_preview: None,
recovered_cargo_slot: None,
recovered_cargo_class: None,
requires_candidate_name_binding: false,
notes: vec![],
}];
let summary = derive_negative_sentinel_scope_summary(
&rows,
&SmpLoadedPackedEventCompactControlSummary {
mode_byte_0x7ef: 6,
primary_selector_0x7f0: 0x63,
grouped_mode_0x7f4: 2,
one_shot_header_0x7f5: 1,
modifier_flag_0x7f9: 4,
modifier_flag_0x7fa: 2,
grouped_target_scope_ordinals_0x7fb: vec![0, 1, 2, 3],
grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0],
summary_toggle_0x800: 1,
grouped_territory_selectors_0x80f: vec![-1, -1, -1, -1],
},
)
.expect("negative sentinel summary should derive");
assert_eq!(
summary.company_test_scope,
RuntimeCompanyConditionTestScope::HumanCompaniesOnly
);
assert_eq!(
summary.player_test_scope,
RuntimePlayerConditionTestScope::SelectedPlayerOnly
);
assert!(summary.territory_scope_selector_is_0x63);
assert_eq!(summary.source_row_indexes, vec![0]);
}
#[test]
fn classifies_real_grouped_row_semantic_families() {
let grouped_rows = vec![
build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 2,
opcode: 1,
raw_scalar_value: 1,
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,
}),
build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 2,
opcode: 4,
raw_scalar_value: 25,
value_byte_0x09: 0,
value_dword_0x0d: 0,
value_byte_0x11: 0,
value_byte_0x12: 0,
value_word_0x14: 2,
value_word_0x16: 6,
locomotive_name: None,
}),
build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 2,
opcode: 3,
raw_scalar_value: 250,
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,
}),
build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 2,
opcode: 8,
raw_scalar_value: 7,
value_byte_0x09: 1,
value_dword_0x0d: 12,
value_byte_0x11: 2,
value_byte_0x12: 3,
value_word_0x14: 24,
value_word_0x16: 36,
locomotive_name: Some("Mikado"),
}),
];
let record_body = build_real_event_record(
[b"Semantic", b"", b"", b"", b"", b""],
Some(RealCompactControlSpec {
mode_byte_0x7ef: 7,
primary_selector_0x7f0: 0x63,
grouped_mode_0x7f4: 1,
one_shot_header_0x7f5: 0,
modifier_flag_0x7f9: 0,
modifier_flag_0x7fa: 0,
grouped_target_scope_ordinals_0x7fb: [1, 1, 1, 1],
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
summary_toggle_0x800: 0,
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
}),
&[],
[&grouped_rows, &[], &[], &[]],
);
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");
let families = summary.records[0]
.grouped_effect_rows
.iter()
.map(|row| row.semantic_family.as_deref().unwrap_or(""))
.collect::<Vec<_>>();
assert_eq!(
families,
vec![
"bool_toggle",
"timed_duration",
"scalar_assignment",
"multivalue_scalar",
]
);
assert_eq!(
summary.records[0].grouped_effect_rows[0]
.semantic_preview
.as_deref(),
Some("Set Company Cash to TRUE")
);
assert_eq!(
summary.records[0].grouped_effect_rows[1]
.semantic_preview
.as_deref(),
Some("Set Company Cash to 25 for 2 years 6 months")
);
assert_eq!(
summary.records[0].grouped_effect_rows[2]
.semantic_preview
.as_deref(),
Some("Set Company Cash to 250")
);
assert_eq!(
summary.records[0].grouped_effect_rows[3]
.semantic_preview
.as_deref(),
Some("Set Company Cash to 7 with aux [2, 3, 24, 36]")
);
}
#[test]
fn rejects_truncated_real_style_event_runtime_record() {
let mut record_body = build_real_event_record(
[b"Oops", b"", b"", b"", b"", b""],
Some(RealCompactControlSpec {
mode_byte_0x7ef: 5,
primary_selector_0x7f0: 0,
grouped_mode_0x7f4: 0,
one_shot_header_0x7f5: 0,
modifier_flag_0x7f9: 0,
modifier_flag_0x7fa: 0,
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
summary_toggle_0x800: 0,
grouped_territory_selectors_0x80f: [0, 0, 0, 0],
}),
&[],
[&[], &[], &[], &[]],
);
record_body.pop();
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].decode_status, "unsupported_framing");
assert_eq!(summary.records[0].payload_family, "unsupported_framing");
}
#[test]
fn loads_event_runtime_collection_summary_from_report() {
let mut report = inspect_smp_bytes(&[]);
let classic_probe = SmpClassicRehydrateProfileProbe {
profile_family: "rt3-classic-save-container-v1".to_string(),
progress_32dc_offset: 0x76e8,
progress_3714_offset: 0x76ec,
progress_3715_offset: 0x77f8,
packed_profile_offset: 0x76f0,
packed_profile_len: 0x108,
packed_profile_len_hex: "0x108".to_string(),
packed_profile_block: SmpClassicPackedProfileBlock {
relative_len: 0x108,
relative_len_hex: "0x108".to_string(),
leading_word_0: 3,
leading_word_0_hex: "0x00000003".to_string(),
trailing_zero_word_count_after_leading_word: 3,
map_path_offset: 0x13,
map_path: Some("British Isles.gmp".to_string()),
display_name_offset: 0x46,
display_name: Some("British Isles".to_string()),
profile_byte_0x77: 0,
profile_byte_0x77_hex: "0x00".to_string(),
profile_byte_0x82: 0,
profile_byte_0x82_hex: "0x00".to_string(),
profile_byte_0x97: 0,
profile_byte_0x97_hex: "0x00".to_string(),
profile_byte_0xc5: 0,
profile_byte_0xc5_hex: "0x00".to_string(),
stable_nonzero_words: vec![],
},
ascii_runs: vec![],
};
report.classic_rehydrate_profile_probe = Some(classic_probe.clone());
report.save_load_summary = build_save_load_summary(
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-classic-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
None,
None,
Some(&classic_probe),
None,
None,
);
report.event_runtime_collection_summary = Some(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: 5,
live_record_count: 3,
live_entry_ids: vec![1, 3, 5],
decoded_record_count: 0,
imported_runtime_record_count: 0,
records: build_unsupported_event_runtime_record_summaries(&[1, 3, 5], "test summary"),
});
let slice = load_save_slice_from_report(&report).expect("classic save slice");
assert_eq!(
slice
.event_runtime_collection
.as_ref()
.map(|summary| summary.live_entry_ids.clone()),
Some(vec![1, 3, 5])
);
}
#[test]
fn loads_rt3_105_save_slice_from_report() {
let mut report = inspect_smp_bytes(&[]);
let packed_profile = SmpRt3105PackedProfileProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
packed_profile_offset: 0x73c0,
packed_profile_len: 0x108,
packed_profile_len_hex: "0x108".to_string(),
packed_profile_block: SmpRt3105PackedProfileBlock {
relative_len: 0x108,
relative_len_hex: "0x108".to_string(),
leading_word_0: 3,
leading_word_0_hex: "0x00000003".to_string(),
trailing_zero_word_count_after_leading_word: 2,
header_flag_word_3: 1,
header_flag_word_3_hex: "0x00000001".to_string(),
map_path_offset: 0x10,
map_path: Some("Alternate USA.gmp".to_string()),
display_name_offset: 0x43,
display_name: Some("Alternate USA".to_string()),
profile_byte_0x77: 0x07,
profile_byte_0x77_hex: "0x07".to_string(),
profile_byte_0x82: 0x4d,
profile_byte_0x82_hex: "0x4d".to_string(),
profile_byte_0x97: 0,
profile_byte_0x97_hex: "0x00".to_string(),
profile_byte_0xc5: 0,
profile_byte_0xc5_hex: "0x00".to_string(),
stable_nonzero_words: vec![],
},
ascii_runs: vec![],
};
let name_table = SmpRt3105SaveNameTableProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-bridge-secondary-block".to_string(),
semantic_family: "scenario-named-candidate-availability-table".to_string(),
semantic_alignment: vec![],
header_offset: 0x6a70,
header_word_0: 0,
header_word_0_hex: "0x00000000".to_string(),
header_word_1: 0,
header_word_1_hex: "0x00000000".to_string(),
header_word_2: 0x332e,
header_word_2_hex: "0x0000332e".to_string(),
entry_stride: 0x22,
entry_stride_hex: "0x22".to_string(),
header_prefix_word_count: 11,
observed_entry_capacity: 0x44,
observed_entry_count: 2,
zero_trailer_entry_count: 1,
nonzero_trailer_entry_count: 1,
distinct_trailer_words: vec![0, 1],
distinct_trailer_hex_words: vec!["0x00000000".to_string(), "0x00000001".to_string()],
zero_trailer_entry_names: vec!["Uranium Mine".to_string()],
entries_offset: 0x6ad1,
entries_end_offset: 0x6b15,
trailing_footer_hex: "dc3200001437000000".to_string(),
footer_progress_word_0: 0x32dc,
footer_progress_word_0_hex: "0x000032dc".to_string(),
footer_progress_word_1: 0x3714,
footer_progress_word_1_hex: "0x00003714".to_string(),
footer_trailing_byte: 0,
footer_trailing_byte_hex: "0x00".to_string(),
footer_grounded_alignments: vec![],
entries: vec![
SmpRt3105SaveNameTableEntry {
index: 0,
offset: 0x6ad1,
text: "AutoPlant".to_string(),
availability_dword: 1,
availability_dword_hex: "0x00000001".to_string(),
trailer_word: 1,
trailer_word_hex: "0x00000001".to_string(),
},
SmpRt3105SaveNameTableEntry {
index: 1,
offset: 0x6af3,
text: "Uranium Mine".to_string(),
availability_dword: 0,
availability_dword_hex: "0x00000000".to_string(),
trailer_word: 0,
trailer_word_hex: "0x00000000".to_string(),
},
],
evidence: vec![],
};
let named_locomotive_table = SmpRt3105SaveNamedLocomotiveAvailabilityProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-direct-locomotive-row-run".to_string(),
semantic_family: "scenario-named-locomotive-availability-table".to_string(),
semantic_alignment: vec![],
entries_offset: 0x7c78,
entry_stride: RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE,
entry_stride_hex: format!("0x{:x}", RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE),
observed_entry_count: 2,
zero_availability_count: 1,
zero_availability_names: vec!["Big Boy".to_string()],
entries_end_offset: 0x7c78 + 2 * RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE,
entries: vec![
SmpRt3105SaveNameTableEntry {
index: 0,
offset: 0x7c78,
text: "Big Boy".to_string(),
availability_dword: 0,
availability_dword_hex: "0x00000000".to_string(),
trailer_word: 0,
trailer_word_hex: "0x00000000".to_string(),
},
SmpRt3105SaveNameTableEntry {
index: 1,
offset: 0x7cb9,
text: "GP7".to_string(),
availability_dword: 1,
availability_dword_hex: "0x00000001".to_string(),
trailer_word: 1,
trailer_word_hex: "0x00000001".to_string(),
},
],
evidence: vec![],
};
let bridge = SmpRt3105PostSpanBridgeProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
bridge_family: "rt3-105-save-post-span-bridge-v1".to_string(),
bridge_evidence: vec![],
span_target_offset: 0x3678,
next_candidate_offset: Some(0x4f14),
next_candidate_delta_from_span_target: Some(0x189c),
packed_profile_offset: 0x73c0,
packed_profile_delta_from_span_target: 0x3d48,
next_candidate_delta_from_packed_profile: Some(-0x24ac),
selector_high_u16: 0x7110,
selector_high_hex: "0x7110".to_string(),
descriptor_high_u16: 0x7801,
descriptor_high_hex: "0x7801".to_string(),
next_candidate_high_u16_words: vec![0x6200, 0x0000, 0xfff7, 0x5515],
next_candidate_high_hex_words: vec![],
};
report.rt3_105_packed_profile_probe = Some(packed_profile.clone());
report.rt3_105_save_name_table_probe = Some(name_table.clone());
report.rt3_105_save_named_locomotive_availability_probe =
Some(named_locomotive_table.clone());
report.save_load_summary = build_save_load_summary(
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
None,
Some(&bridge),
None,
Some(&packed_profile),
Some(&name_table),
);
let slice = load_save_slice_from_report(&report).expect("1.05 save slice");
assert_eq!(slice.mechanism_family, "rt3-105-save-post-span-bridge-v1");
assert_eq!(
slice
.profile
.as_ref()
.and_then(|profile| profile.map_path.as_deref()),
Some("Alternate USA.gmp")
);
assert_eq!(
slice
.candidate_availability_table
.as_ref()
.expect("candidate table")
.entries[1]
.text,
"Uranium Mine"
);
assert_eq!(
slice
.named_locomotive_availability_table
.as_ref()
.expect("named locomotive availability table")
.entries[1]
.text,
"GP7"
);
assert_eq!(
slice
.locomotive_catalog
.as_ref()
.expect("derived locomotive catalog")
.entries[0]
.name,
"Big Boy"
);
assert_eq!(
slice
.locomotive_catalog
.as_ref()
.expect("derived locomotive catalog")
.entries[1]
.locomotive_id,
2
);
}
#[test]
fn parses_save_world_selection_context_probe_from_fixed_world_block() {
let mut bytes = vec![0u8; 0x8000];
let chunk_tag_offset = 0x3ceusize;
let payload_offset = chunk_tag_offset + 4;
bytes[chunk_tag_offset..chunk_tag_offset + 4]
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_CHUNK_TAG.to_le_bytes());
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET
..payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET + 4]
.copy_from_slice(&7u32.to_le_bytes());
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET
..payload_offset
+ RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET
+ 4]
.copy_from_slice(&9u32.to_le_bytes());
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_SELECTOR_RELATIVE_OFFSET
..payload_offset
+ RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_SELECTOR_RELATIVE_OFFSET
+ RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_COUNT]
.copy_from_slice(&[3, 1, 4, 1, 5, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_CAMPAIGN_OVERRIDE_FLAG_RELATIVE_OFFSET] = 1;
for (slot_index, role_gate) in [2u8, 1, 0, 2].into_iter().enumerate() {
bytes[payload_offset
+ RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_RELATIVE_OFFSET
+ slot_index * RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_STRIDE] = role_gate;
}
let next_chunk_offset = payload_offset + RT3_SAVE_WORLD_BLOCK_LEN;
bytes[next_chunk_offset..next_chunk_offset + 4]
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG.to_le_bytes());
let probe = parse_save_world_selection_context_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
)
.expect("selection-context probe should parse");
assert_eq!(probe.chunk_tag_offset, chunk_tag_offset);
assert_eq!(probe.payload_offset, payload_offset);
assert_eq!(probe.selected_company_id, 7);
assert_eq!(probe.selected_chairman_profile_id, 9);
assert_eq!(probe.chairman_slot_selectors[..6], [3, 1, 4, 1, 5, 9]);
assert_eq!(probe.campaign_override_flag, 1);
assert_eq!(probe.chairman_role_gate_bytes[..4], [2, 1, 0, 2]);
}
#[test]
fn parses_save_world_economic_tuning_probe_from_fixed_world_block() {
let mut bytes = vec![0u8; 0x8000];
let chunk_tag_offset = 0x3ceusize;
let payload_offset = chunk_tag_offset + 4;
bytes[chunk_tag_offset..chunk_tag_offset + 4]
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_CHUNK_TAG.to_le_bytes());
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET
..payload_offset + RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET + 4]
.copy_from_slice(&1.5f32.to_bits().to_le_bytes());
for (lane_index, relative_offset) in
RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_PRIMARY_RELATIVE_OFFSETS
.iter()
.copied()
.enumerate()
{
let value = (lane_index as f32) + 10.25f32;
bytes[payload_offset + relative_offset..payload_offset + relative_offset + 4]
.copy_from_slice(&value.to_bits().to_le_bytes());
}
let next_chunk_offset = payload_offset + RT3_SAVE_WORLD_BLOCK_LEN;
bytes[next_chunk_offset..next_chunk_offset + 4]
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG.to_le_bytes());
let probe = parse_save_world_economic_tuning_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
)
.expect("world economic tuning probe should parse");
assert_eq!(probe.chunk_tag_offset, chunk_tag_offset);
assert_eq!(probe.payload_offset, payload_offset);
assert_eq!(probe.mirror_lane.relative_offset_hex, "0xbda");
assert_eq!(probe.mirror_lane.value_f32, 1.5);
assert_eq!(probe.tuning_lanes.len(), 6);
assert_eq!(probe.tuning_lanes[0].relative_offset_hex, "0xbde");
assert_eq!(probe.tuning_lanes[0].value_f32, 10.25);
assert_eq!(probe.tuning_lanes[5].relative_offset_hex, "0xbf2");
assert_eq!(probe.tuning_lanes[5].value_f32, 15.25);
}
#[test]
fn parses_save_world_issue_37_probe_from_fixed_world_block() {
let mut bytes = vec![0u8; 0x8000];
let chunk_tag_offset = 0x3ceusize;
let payload_offset = chunk_tag_offset + 4;
bytes[chunk_tag_offset..chunk_tag_offset + 4]
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_CHUNK_TAG.to_le_bytes());
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET
..payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET + 4]
.copy_from_slice(&3u32.to_le_bytes());
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_MULTIPLIER_RELATIVE_OFFSET
..payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_MULTIPLIER_RELATIVE_OFFSET + 4]
.copy_from_slice(&0.06f32.to_bits().to_le_bytes());
let next_chunk_offset = payload_offset + RT3_SAVE_WORLD_BLOCK_LEN;
bytes[next_chunk_offset..next_chunk_offset + 4]
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG.to_le_bytes());
let probe = parse_save_world_issue_37_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
)
.expect("world issue-0x37 probe should parse");
assert_eq!(probe.chunk_tag_offset, chunk_tag_offset);
assert_eq!(probe.payload_offset, payload_offset);
assert_eq!(probe.issue_value_lane.relative_offset_hex, "0x29");
assert_eq!(probe.issue_value_lane.value_i32, 3);
assert_eq!(probe.issue_37_raw_u8, 3);
assert_eq!(probe.issue_37_raw_hex, "0x03");
assert_eq!(probe.issue_38_raw_u8, 0);
assert_eq!(probe.issue_38_raw_hex, "0x00");
assert_eq!(probe.issue_39_raw_u8, 0);
assert_eq!(probe.issue_39_raw_hex, "0x00");
assert_eq!(probe.issue_3a_raw_u8, 0);
assert_eq!(probe.issue_3a_raw_hex, "0x00");
assert_eq!(probe.multiplier_lane.relative_offset_hex, "0x25");
assert!((probe.multiplier_lane.value_f32 - 0.06).abs() < f32::EPSILON);
}
#[test]
fn parses_save_world_finance_neighborhood_probe_from_fixed_world_block() {
let mut bytes = vec![0u8; 0x8000];
let chunk_tag_offset = 0x3ceusize;
let payload_offset = chunk_tag_offset + 4;
bytes[chunk_tag_offset..chunk_tag_offset + 4]
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_CHUNK_TAG.to_le_bytes());
for index in 0..RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_WINDOW_LEN_DWORDS {
let relative_offset =
RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_ROOT_RELATIVE_OFFSET + index * 4;
bytes[payload_offset + relative_offset..payload_offset + relative_offset + 4]
.copy_from_slice(&((index as u32) + 1).to_le_bytes());
}
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_STOCK_POLICY_RELATIVE_OFFSET] = 1;
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_BOND_POLICY_RELATIVE_OFFSET] = 2;
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_BANKRUPTCY_POLICY_RELATIVE_OFFSET] = 3;
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET] = 4;
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET
..payload_offset + RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET + 4]
.copy_from_slice(&2u32.to_le_bytes());
let next_chunk_offset = payload_offset + RT3_SAVE_WORLD_BLOCK_LEN;
bytes[next_chunk_offset..next_chunk_offset + 4]
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG.to_le_bytes());
let probe = parse_save_world_finance_neighborhood_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
)
.expect("world finance neighborhood probe should parse");
assert_eq!(probe.chunk_tag_offset, chunk_tag_offset);
assert_eq!(probe.payload_offset, payload_offset);
assert_eq!(
probe.dword_candidates.len(),
RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_WINDOW_LEN_DWORDS
);
assert_eq!(
probe.current_calendar_tuple_word_lane.relative_offset_hex,
"0xd"
);
assert_eq!(probe.packed_year_word_raw_u16, 1);
assert_eq!(probe.packed_year_word_raw_hex, "0x0001");
assert_eq!(probe.partial_year_progress_raw_u8, 0);
assert_eq!(probe.partial_year_progress_raw_hex, "0x00");
assert_eq!(probe.stock_policy_raw_u8, 1);
assert_eq!(probe.stock_policy_raw_hex, "0x01");
assert_eq!(probe.bond_policy_raw_u8, 2);
assert_eq!(probe.bond_policy_raw_hex, "0x02");
assert_eq!(probe.bankruptcy_policy_raw_u8, 3);
assert_eq!(probe.bankruptcy_policy_raw_hex, "0x03");
assert_eq!(probe.dividend_policy_raw_u8, 4);
assert_eq!(probe.dividend_policy_raw_hex, "0x04");
assert_eq!(probe.building_density_growth_setting_lane.raw_u32, 2);
assert_eq!(
probe
.building_density_growth_setting_lane
.relative_offset_hex,
"0x4c78"
);
assert_eq!(probe.current_calendar_tuple_word_lane.value_i32, 1);
assert_eq!(
probe.current_calendar_tuple_word_2_lane.relative_offset_hex,
"0x11"
);
assert_eq!(probe.current_calendar_tuple_word_2_lane.value_i32, 2);
assert_eq!(probe.absolute_counter_lane.relative_offset_hex, "0x15");
assert_eq!(probe.absolute_counter_lane.value_i32, 3);
assert_eq!(
probe.absolute_counter_mirror_lane.relative_offset_hex,
"0x19"
);
assert_eq!(probe.absolute_counter_mirror_lane.value_i32, 4);
assert_eq!(
probe.dword_candidates[0].label,
"current_calendar_tuple_word"
);
assert_eq!(probe.dword_candidates[0].relative_offset_hex, "0xd");
assert_eq!(probe.dword_candidates[0].value_i32, 1);
assert_eq!(probe.dword_candidates[6].label, "issue_0x37_multiplier");
assert_eq!(probe.dword_candidates[6].relative_offset_hex, "0x25");
assert_eq!(
probe.dword_candidates[10].label,
"issue_neighbor_candidate_2"
);
assert_eq!(probe.dword_candidates[10].relative_offset_hex, "0x35");
assert_eq!(probe.dword_candidates[10].value_i32, 11);
assert_eq!(
probe.dword_candidates[11].label,
"finance_neighborhood_word_12"
);
assert_eq!(probe.dword_candidates[11].relative_offset_hex, "0x39");
assert_eq!(probe.dword_candidates[11].value_i32, 12);
assert_eq!(
probe.dword_candidates[16].label,
"finance_neighborhood_word_17"
);
assert_eq!(probe.dword_candidates[16].relative_offset_hex, "0x4d");
assert_eq!(probe.dword_candidates[16].value_i32, 17);
}
#[test]
fn loads_selection_only_company_and_chairman_context_from_save_world_probe() {
let mut report = inspect_smp_bytes(&[]);
report.save_load_summary = Some(SmpSaveLoadSummary {
file_extension_hint: Some("gms".to_string()),
container_profile_family: Some("rt3-105-save-container-v1".to_string()),
mechanism_family: "rt3-105-save-post-span-bridge-v1".to_string(),
mechanism_confidence: "mixed".to_string(),
packed_profile_kind: None,
packed_profile_family: None,
packed_profile_offset: None,
packed_profile_len: None,
map_path: None,
display_name: None,
profile_byte_0x77: None,
profile_byte_0x77_hex: None,
profile_byte_0x82: None,
profile_byte_0x82_hex: None,
profile_byte_0x97: None,
profile_byte_0x97_hex: None,
profile_byte_0xc5: None,
profile_byte_0xc5_hex: None,
trailer_family: Some("rt3-105-save-trailer-v1".to_string()),
bridge_family: Some("rt3-105-save-post-span-bridge-v1".to_string()),
candidate_table: None,
notes: vec![],
});
report.save_world_selection_context_probe = Some(SmpSaveWorldSelectionContextProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-direct-world-block".to_string(),
semantic_family: "scenario-selected-company-and-chairman-context".to_string(),
chunk_tag_offset: 0x3ce,
payload_offset: 0x3d2,
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
selected_company_id_offset: 0x3ef,
selected_company_id: 1,
selected_company_id_hex: "0x00000001".to_string(),
selected_chairman_profile_id_offset: 0x3f3,
selected_chairman_profile_id: 9,
selected_chairman_profile_id_hex: "0x00000009".to_string(),
chairman_slot_selector_offset: 0x455,
chairman_slot_selectors: vec![3, 1, 4, 1, 5, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
campaign_override_flag_offset: 0x493,
campaign_override_flag: 1,
campaign_override_flag_hex: "0x01".to_string(),
chairman_role_gate_offset: 0xf91,
chairman_role_gate_bytes: vec![2, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
evidence: vec![],
});
report.save_world_issue_37_probe = Some(SmpSaveWorldIssue37Probe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-direct-world-block".to_string(),
semantic_family: "scenario-save-world-issue-0x37".to_string(),
chunk_tag_offset: 0x3ce,
payload_offset: 0x3d2,
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
issue_37_raw_u8: 3,
issue_37_raw_hex: "0x03".to_string(),
issue_38_raw_u8: 1,
issue_38_raw_hex: "0x01".to_string(),
issue_39_raw_u8: 2,
issue_39_raw_hex: "0x02".to_string(),
issue_3a_raw_u8: 4,
issue_3a_raw_hex: "0x04".to_string(),
issue_value_lane: SmpSaveDwordCandidate {
label: "issue_0x37_value".to_string(),
relative_offset: 0x29,
relative_offset_hex: "0x29".to_string(),
raw_u32: 3,
raw_u32_hex: "0x00000003".to_string(),
value_i32: 3,
value_f32: f32::from_bits(3),
},
multiplier_lane: SmpSaveDwordCandidate {
label: "issue_0x37_multiplier".to_string(),
relative_offset: 0x25,
relative_offset_hex: "0x25".to_string(),
raw_u32: 0x3d75c28f,
raw_u32_hex: "0x3d75c28f".to_string(),
value_i32: 1031127695,
value_f32: 0.06,
},
issue_opinion_base_terms_raw_i32: Vec::new(),
evidence: vec![],
});
report.save_world_economic_tuning_probe = Some(SmpSaveWorldEconomicTuningProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-direct-world-block".to_string(),
semantic_family: "scenario-save-world-economic-tuning".to_string(),
chunk_tag_offset: 0x3ce,
payload_offset: 0x3d2,
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
mirror_lane: SmpSaveDwordCandidate {
label: "economic_tuning_mirror_lane_0".to_string(),
relative_offset: 0xbda,
relative_offset_hex: "0xbda".to_string(),
raw_u32: 0x3f46d093,
raw_u32_hex: "0x3f46d093".to_string(),
value_i32: 1061605523,
value_f32: 0.7766201,
},
tuning_lanes: vec![
SmpSaveDwordCandidate {
label: "economic_tuning_lane_0".to_string(),
relative_offset: 0xbde,
relative_offset_hex: "0xbde".to_string(),
raw_u32: 0x3f400000,
raw_u32_hex: "0x3f400000".to_string(),
value_i32: 1061158912,
value_f32: 0.75,
},
SmpSaveDwordCandidate {
label: "economic_tuning_lane_1".to_string(),
relative_offset: 0xbe2,
relative_offset_hex: "0xbe2".to_string(),
raw_u32: 0x3be56042,
raw_u32_hex: "0x3be56042".to_string(),
value_i32: 1004888130,
value_f32: 0.007,
},
],
evidence: vec![],
});
report.save_company_collection_header_probe = Some(SmpSaveTaggedCollectionHeaderProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-company-tagged-header-counts".to_string(),
semantic_family: "scenario-save-company-header-counts".to_string(),
metadata_tag_offset: 0x1000,
records_tag_offset: 0x1100,
close_tag_offset: 0x1200,
direct_collection_flag: 1,
direct_collection_flag_hex: "0x00000001".to_string(),
direct_record_stride: 0x7684,
direct_record_stride_hex: "0x00007684".to_string(),
live_id_bound: 5,
live_id_bound_hex: "0x00000005".to_string(),
live_record_count: 1,
live_record_count_hex: "0x00000001".to_string(),
header_words: vec![1, 0x7684, 5, 5, 5, 1],
header_hex_words: vec![
"0x00000001".to_string(),
"0x00007684".to_string(),
"0x00000005".to_string(),
"0x00000005".to_string(),
"0x00000005".to_string(),
"0x00000001".to_string(),
],
evidence: vec![],
});
report.save_chairman_profile_collection_header_probe =
Some(SmpSaveTaggedCollectionHeaderProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-chairman-profile-tagged-header-counts".to_string(),
semantic_family: "scenario-save-chairman-profile-header-counts".to_string(),
metadata_tag_offset: 0x2000,
records_tag_offset: 0x2100,
close_tag_offset: 0x2200,
direct_collection_flag: 1,
direct_collection_flag_hex: "0x00000001".to_string(),
direct_record_stride: 0xcab,
direct_record_stride_hex: "0x00000cab".to_string(),
live_id_bound: 8,
live_id_bound_hex: "0x00000008".to_string(),
live_record_count: 2,
live_record_count_hex: "0x00000002".to_string(),
header_words: vec![1, 0xcab, 8, 6, 8, 2],
header_hex_words: vec![
"0x00000001".to_string(),
"0x00000cab".to_string(),
"0x00000008".to_string(),
"0x00000006".to_string(),
"0x00000008".to_string(),
"0x00000002".to_string(),
],
evidence: vec![],
});
report.save_region_collection_header_probe = Some(SmpSaveTaggedCollectionHeaderProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-region-tagged-header-counts".to_string(),
semantic_family: "scenario-save-region-header-counts".to_string(),
metadata_tag_offset: 0x3000,
records_tag_offset: 0x3100,
close_tag_offset: 0x3200,
direct_collection_flag: 1,
direct_collection_flag_hex: "0x00000001".to_string(),
direct_record_stride: 0x1d5,
direct_record_stride_hex: "0x000001d5".to_string(),
live_id_bound: 0x32,
live_id_bound_hex: "0x00000032".to_string(),
live_record_count: 0x14,
live_record_count_hex: "0x00000014".to_string(),
header_words: vec![1, 0x1d5, 0x32, 0x14, 0x32, 0x14],
header_hex_words: vec![
"0x00000001".to_string(),
"0x000001d5".to_string(),
"0x00000032".to_string(),
"0x00000014".to_string(),
"0x00000032".to_string(),
"0x00000014".to_string(),
],
evidence: vec![],
});
report.save_region_collection_directory_probe =
Some(SmpSaveRegionCollectionDirectoryProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-region-live-directory".to_string(),
semantic_family: "scenario-save-region-live-directory".to_string(),
metadata_tag_offset: 0x3000,
records_tag_offset: 0x3100,
close_tag_offset: 0x3200,
directory_root_dword_index: 16,
directory_entry_dword_count: 3,
live_record_count: 0x14,
live_id_bound: 0x32,
chain_head_live_entry_id: Some(1),
chain_tail_live_entry_id: Some(20),
entries: vec![
SmpSaveRegionCollectionDirectoryEntryProbe {
live_entry_id: 1,
payload_relative_offset: 0x2af8,
payload_relative_offset_hex: "0x00002af8".to_string(),
payload_absolute_offset: 0x5afc,
previous_live_entry_id: 0,
previous_live_entry_id_hex: "0x00000000".to_string(),
next_live_entry_id: 2,
next_live_entry_id_hex: "0x00000002".to_string(),
},
SmpSaveRegionCollectionDirectoryEntryProbe {
live_entry_id: 2,
payload_relative_offset: 0x2ee0,
payload_relative_offset_hex: "0x00002ee0".to_string(),
payload_absolute_offset: 0x5ee4,
previous_live_entry_id: 1,
previous_live_entry_id_hex: "0x00000001".to_string(),
next_live_entry_id: 0,
next_live_entry_id_hex: "0x00000000".to_string(),
},
],
evidence: vec![],
});
report.save_placed_structure_collection_header_probe =
Some(SmpSaveTaggedCollectionHeaderProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-placed-structure-tagged-header-counts".to_string(),
semantic_family: "scenario-save-placed-structure-header-counts".to_string(),
metadata_tag_offset: 0x4000,
records_tag_offset: 0x4100,
close_tag_offset: 0x4200,
direct_collection_flag: 0,
direct_collection_flag_hex: "0x00000000".to_string(),
direct_record_stride: 0x06,
direct_record_stride_hex: "0x00000006".to_string(),
live_id_bound: 0x7ee,
live_id_bound_hex: "0x000007ee".to_string(),
live_record_count: 0x7ea,
live_record_count_hex: "0x000007ea".to_string(),
header_words: vec![0, 6, 0x0a, 0x14, 0x7ee, 0x7ea],
header_hex_words: vec![
"0x00000000".to_string(),
"0x00000006".to_string(),
"0x0000000a".to_string(),
"0x00000014".to_string(),
"0x000007ee".to_string(),
"0x000007ea".to_string(),
],
evidence: vec![],
});
let slice = load_save_slice_from_report(&report).expect("save slice");
let company_roster = slice.company_roster.expect("selection-only company roster");
assert_eq!(company_roster.observed_entry_count, 1);
assert_eq!(company_roster.selected_company_id, Some(1));
assert!(company_roster.entries.is_empty());
let chairman_table = slice
.chairman_profile_table
.expect("selection-only chairman table");
assert_eq!(chairman_table.observed_entry_count, 2);
assert_eq!(chairman_table.selected_chairman_profile_id, Some(9));
assert!(chairman_table.entries.is_empty());
let issue_37_state = slice
.world_issue_37_state
.expect("world issue-0x37 state should load");
assert_eq!(issue_37_state.issue_value, 3);
assert_eq!(issue_37_state.issue_38_value, 1);
assert_eq!(issue_37_state.issue_39_value, 2);
assert_eq!(issue_37_state.issue_3a_value, 4);
assert_eq!(issue_37_state.multiplier_raw_hex, "0x3d75c28f");
assert_eq!(issue_37_state.multiplier_value_f32_text, "0.060000");
let tuning_state = slice
.world_economic_tuning_state
.expect("world economic tuning state should load");
assert_eq!(tuning_state.mirror_raw_hex, "0x3f46d093");
assert_eq!(tuning_state.mirror_value_f32_text, "0.776620");
assert_eq!(
tuning_state.lane_value_f32_text,
vec!["0.750000", "0.007000"]
);
assert!(
slice
.notes
.iter()
.any(|note| note.contains("selected_company_id=1"))
);
assert!(
slice
.notes
.iter()
.any(|note| note.contains("selected_chairman_profile_id=9"))
);
assert!(
slice
.notes
.iter()
.any(|note| note.contains("grounded issue-0x37 pair: value=3"))
);
assert!(
slice
.notes
.iter()
.any(|note| note.contains("campaign_override_flag=1"))
);
assert!(
slice
.notes
.iter()
.any(|note| note.contains("tagged company header reports live_record_count=1"))
);
assert!(slice.notes.iter().any(|note| {
note.contains("tagged chairman/profile header reports live_record_count=2")
}));
assert!(
slice
.notes
.iter()
.any(|note| note.contains("tagged region header reports live_record_count=20"))
);
assert!(slice.notes.iter().any(|note| {
note.contains("tagged region metadata also exposes a live-entry directory")
}));
assert!(slice.notes.iter().any(|note| {
note.contains("tagged placed-structure header reports live_record_count=2026")
}));
}
#[test]
fn parses_company_tagged_collection_header_probe_from_exact_u32_tags() {
let mut bytes = vec![0u8; 0x400];
let metadata_tag_offset = 0x40usize;
let records_tag_offset = 0x140usize;
let close_tag_offset = 0x180usize;
bytes[metadata_tag_offset..metadata_tag_offset + 4]
.copy_from_slice(&0x000061a9u32.to_le_bytes());
bytes[records_tag_offset..records_tag_offset + 4]
.copy_from_slice(&0x000061aau32.to_le_bytes());
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x000061abu32.to_le_bytes());
let header_words = [
1u32, 0x7684, 5, 5, 5, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
];
for (index, word) in header_words.into_iter().enumerate() {
let offset = metadata_tag_offset + 4 + index * 4;
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
}
let probe = parse_save_company_collection_header_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
)
.expect("company header probe should parse");
assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
assert_eq!(probe.records_tag_offset, records_tag_offset);
assert_eq!(probe.close_tag_offset, close_tag_offset);
assert_eq!(probe.direct_record_stride, 0x7684);
assert_eq!(probe.live_id_bound, 5);
assert_eq!(probe.live_record_count, 1);
}
#[test]
fn parses_chairman_profile_tagged_collection_header_probe_from_exact_u32_tags() {
let mut bytes = vec![0u8; 0x400];
let metadata_tag_offset = 0x40usize;
let records_tag_offset = 0x140usize;
let close_tag_offset = 0x180usize;
bytes[metadata_tag_offset..metadata_tag_offset + 4]
.copy_from_slice(&0x00005209u32.to_le_bytes());
bytes[records_tag_offset..records_tag_offset + 4]
.copy_from_slice(&0x0000520au32.to_le_bytes());
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
let header_words = [
1u32, 0xcab, 8, 6, 8, 2, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
];
for (index, word) in header_words.into_iter().enumerate() {
let offset = metadata_tag_offset + 4 + index * 4;
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
}
let probe = parse_save_chairman_profile_collection_header_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
)
.expect("chairman profile header probe should parse");
assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
assert_eq!(probe.records_tag_offset, records_tag_offset);
assert_eq!(probe.close_tag_offset, close_tag_offset);
assert_eq!(probe.direct_record_stride, 0xcab);
assert_eq!(probe.live_id_bound, 8);
assert_eq!(probe.live_record_count, 2);
}
#[test]
fn parses_region_tagged_collection_header_probe_from_exact_u32_tags() {
let mut bytes = vec![0u8; 0x400];
let metadata_tag_offset = 0x40usize;
let records_tag_offset = 0x140usize;
let close_tag_offset = 0x180usize;
bytes[metadata_tag_offset..metadata_tag_offset + 4]
.copy_from_slice(&0x00005209u32.to_le_bytes());
bytes[records_tag_offset..records_tag_offset + 4]
.copy_from_slice(&0x0000520au32.to_le_bytes());
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
let header_words = [
1u32, 0x1d5, 0x32, 0x14, 0x32, 0x14, 0x14, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
];
for (index, word) in header_words.into_iter().enumerate() {
let offset = metadata_tag_offset + 4 + index * 4;
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
}
let probe = parse_save_region_collection_header_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
)
.expect("region header probe should parse");
assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
assert_eq!(probe.records_tag_offset, records_tag_offset);
assert_eq!(probe.close_tag_offset, close_tag_offset);
assert_eq!(probe.direct_collection_flag, 1);
assert_eq!(probe.direct_record_stride, 0x1d5);
assert_eq!(probe.live_id_bound, 0x32);
assert_eq!(probe.live_record_count, 0x14);
}
#[test]
fn parses_region_collection_directory_probe_from_tagged_metadata_triplets() {
let mut bytes = vec![0u8; 0x400];
let metadata_tag_offset = 0x40usize;
let records_tag_offset = 0x180usize;
let close_tag_offset = 0x1c0usize;
bytes[metadata_tag_offset..metadata_tag_offset + 4]
.copy_from_slice(&0x00005209u32.to_le_bytes());
bytes[records_tag_offset..records_tag_offset + 4]
.copy_from_slice(&0x0000520au32.to_le_bytes());
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
let header_words = [
1u32, 0x1d5, 0x32, 0x03, 0x32, 0x03, 0x14, 1, 0, 0, 1, 1, 0x14, 0, 0, 0,
];
for (index, word) in header_words.into_iter().enumerate() {
let offset = metadata_tag_offset + 4 + index * 4;
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
}
let triplets = [(0x2af8u32, 0u32, 2u32), (0x2ee0, 1, 3), (0x32c8, 2, 0)];
for (index, (offset_word, prev, next)) in triplets.into_iter().enumerate() {
let base = metadata_tag_offset
+ 4
+ (SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX + index * 3) * 4;
bytes[base..base + 4].copy_from_slice(&offset_word.to_le_bytes());
bytes[base + 4..base + 8].copy_from_slice(&prev.to_le_bytes());
bytes[base + 8..base + 12].copy_from_slice(&next.to_le_bytes());
}
let header_probe = parse_save_region_collection_header_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
)
.expect("region header probe should parse");
let directory_probe =
parse_save_region_collection_directory_probe(&bytes, Some(&header_probe))
.expect("region directory probe should parse");
assert_eq!(directory_probe.directory_root_dword_index, 16);
assert_eq!(directory_probe.live_record_count, 3);
assert_eq!(directory_probe.chain_head_live_entry_id, Some(1));
assert_eq!(directory_probe.chain_tail_live_entry_id, Some(3));
assert_eq!(directory_probe.entries.len(), 3);
assert_eq!(directory_probe.entries[0].live_entry_id, 1);
assert_eq!(directory_probe.entries[0].payload_relative_offset, 0x2af8);
assert_eq!(directory_probe.entries[0].previous_live_entry_id, 0);
assert_eq!(directory_probe.entries[0].next_live_entry_id, 2);
assert_eq!(directory_probe.entries[2].live_entry_id, 3);
assert_eq!(directory_probe.entries[2].previous_live_entry_id, 2);
assert_eq!(directory_probe.entries[2].next_live_entry_id, 0);
}
#[test]
fn parses_placed_structure_tagged_collection_header_probe_from_exact_u32_tags() {
let mut bytes = vec![0u8; 0x400];
let metadata_tag_offset = 0x40usize;
let records_tag_offset = 0x140usize;
let close_tag_offset = 0x180usize;
bytes[metadata_tag_offset..metadata_tag_offset + 4]
.copy_from_slice(&0x000036b1u32.to_le_bytes());
bytes[records_tag_offset..records_tag_offset + 4]
.copy_from_slice(&0x000036b2u32.to_le_bytes());
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x000036b3u32.to_le_bytes());
let header_words = [
0u32, 0x06, 0x0a, 0x14, 0x7ee, 0x7ea, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
for (index, word) in header_words.into_iter().enumerate() {
let offset = metadata_tag_offset + 4 + index * 4;
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
}
let probe = parse_save_placed_structure_collection_header_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
)
.expect("placed structure header probe should parse");
assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
assert_eq!(probe.records_tag_offset, records_tag_offset);
assert_eq!(probe.close_tag_offset, close_tag_offset);
assert_eq!(probe.direct_collection_flag, 0);
assert_eq!(probe.direct_record_stride, 0x06);
assert_eq!(probe.live_id_bound, 0x7ee);
assert_eq!(probe.live_record_count, 0x7ea);
}
#[test]
fn parses_save_company_roster_probe_from_direct_records() {
let metadata_tag_offset = 0x40usize;
let stride = 0x7684usize;
let count = 2usize;
let start_offset = 0xc6usize;
let total_len = metadata_tag_offset + 4 + start_offset + stride * count + 0x400;
let mut bytes = vec![0u8; total_len];
bytes[metadata_tag_offset..metadata_tag_offset + 4]
.copy_from_slice(&0x000061a9u32.to_le_bytes());
let header_words = [
1u32,
0x7684,
5,
5,
5,
count as u32,
1,
1,
0,
0,
1,
1,
1,
0,
0,
0,
0,
0,
0,
];
for (index, word) in header_words.into_iter().enumerate() {
let offset = metadata_tag_offset + 4 + index * 4;
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
}
let records_tag_offset = metadata_tag_offset + 4 + 0x200;
let close_tag_offset = records_tag_offset + 4;
bytes[records_tag_offset..records_tag_offset + 4]
.copy_from_slice(&0x000061aau32.to_le_bytes());
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x000061abu32.to_le_bytes());
for (
index,
(
name,
linked,
merger,
takeover,
bond_count,
_debt,
track_capacity,
mutable_support_scalar_raw_u32,
young_company_support_scalar_raw_u32,
support_progress_word,
recent_per_share_subscore_raw_u32,
cached_share_price_raw_u32,
chairman_salary_baseline,
chairman_salary_current,
chairman_bonus_year,
chairman_bonus_amount,
founding_year,
last_bankruptcy_year,
last_dividend_year,
current_issue_calendar_word,
current_issue_calendar_word_2,
prior_issue_calendar_word,
prior_issue_calendar_word_2,
preferred_locomotive_engine_type_raw_u8,
city_connection_latch,
linked_transit_latch,
),
) in [
(
"Company One",
1u32,
1862u32,
1865u32,
2u8,
1_000_000u32,
Some(603i32),
0x3f800000u32,
0x42340000u32,
17u32,
0x41f00000u32,
0x426c0000u32,
24u32,
31u32,
1849u32,
1250i32,
1842u32,
1851u32,
1848u32,
7u32,
8u32,
6u32,
7u32,
2u8,
true,
false,
),
(
"Company Two",
2u32,
0u32,
1871u32,
1u8,
500_000u32,
None,
0x40000000u32,
0x42700000u32,
33u32,
0x42000000u32,
0x42780000u32,
28u32,
36u32,
0u32,
0i32,
1845u32,
0u32,
1850u32,
3u32,
4u32,
2u32,
3u32,
1u8,
false,
true,
),
]
.into_iter()
.enumerate()
{
let record_offset = metadata_tag_offset + 4 + start_offset + index * stride;
bytes[record_offset..record_offset + 4]
.copy_from_slice(&((index + 1) as u32).to_le_bytes());
bytes[record_offset + 4..record_offset + 4 + name.len()]
.copy_from_slice(name.as_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_LINKED_CHAIRMAN_OFFSET
..record_offset + SAVE_COMPANY_RECORD_LINKED_CHAIRMAN_OFFSET + 4]
.copy_from_slice(&linked.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_ACTIVE_OFFSET] = 1;
bytes[record_offset + 0x47..record_offset + 0x4b]
.copy_from_slice(&20000u32.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_SUPPORT_SCALAR_OFFSET
..record_offset + SAVE_COMPANY_RECORD_SUPPORT_SCALAR_OFFSET + 4]
.copy_from_slice(&mutable_support_scalar_raw_u32.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_COMPANY_VALUE_OFFSET
..record_offset + SAVE_COMPANY_RECORD_COMPANY_VALUE_OFFSET + 4]
.copy_from_slice(&young_company_support_scalar_raw_u32.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_BOND_COUNT_OFFSET] = bond_count;
for slot_index in 0..bond_count as usize {
let slot_offset = record_offset
+ SAVE_COMPANY_RECORD_BOND_TABLE_OFFSET
+ slot_index * SAVE_COMPANY_RECORD_BOND_SLOT_STRIDE;
let (principal, coupon_rate) = if index == 0 && slot_index == 0 {
(900_000i32, 0.08f32)
} else if index == 0 && slot_index == 1 {
(650_000i32, 0.12f32)
} else {
(500_000i32, 0.10f32)
};
bytes[slot_offset..slot_offset + 4].copy_from_slice(&principal.to_le_bytes());
bytes[slot_offset + 4..slot_offset + 8]
.copy_from_slice(&(1894u32 + slot_index as u32).to_le_bytes());
bytes[slot_offset + 8..slot_offset + 12]
.copy_from_slice(&coupon_rate.to_le_bytes());
}
bytes[record_offset + SAVE_COMPANY_RECORD_MERGER_COOLDOWN_OFFSET
..record_offset + SAVE_COMPANY_RECORD_MERGER_COOLDOWN_OFFSET + 4]
.copy_from_slice(&merger.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_TAKEOVER_COOLDOWN_OFFSET
..record_offset + SAVE_COMPANY_RECORD_TAKEOVER_COOLDOWN_OFFSET + 4]
.copy_from_slice(&takeover.to_le_bytes());
let raw_capacity = track_capacity.unwrap_or(-1);
bytes[record_offset + SAVE_COMPANY_RECORD_TRACK_LAYING_CAPACITY_OFFSET
..record_offset + SAVE_COMPANY_RECORD_TRACK_LAYING_CAPACITY_OFFSET + 4]
.copy_from_slice(&raw_capacity.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_SUPPORT_PROGRESS_OFFSET
..record_offset + SAVE_COMPANY_RECORD_SUPPORT_PROGRESS_OFFSET + 4]
.copy_from_slice(&support_progress_word.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_RECENT_PER_SHARE_SUBSCORE_OFFSET
..record_offset + SAVE_COMPANY_RECORD_RECENT_PER_SHARE_SUBSCORE_OFFSET + 4]
.copy_from_slice(&recent_per_share_subscore_raw_u32.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_CACHED_SHARE_PRICE_OFFSET
..record_offset + SAVE_COMPANY_RECORD_CACHED_SHARE_PRICE_OFFSET + 4]
.copy_from_slice(&cached_share_price_raw_u32.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_BASELINE_OFFSET
..record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_BASELINE_OFFSET + 4]
.copy_from_slice(&chairman_salary_baseline.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_CURRENT_OFFSET
..record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_CURRENT_OFFSET + 4]
.copy_from_slice(&chairman_salary_current.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_YEAR_OFFSET
..record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_YEAR_OFFSET + 4]
.copy_from_slice(&chairman_bonus_year.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_AMOUNT_OFFSET
..record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_AMOUNT_OFFSET + 4]
.copy_from_slice(&chairman_bonus_amount.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_FOUNDING_YEAR_OFFSET
..record_offset + SAVE_COMPANY_RECORD_FOUNDING_YEAR_OFFSET + 4]
.copy_from_slice(&founding_year.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_LAST_BANKRUPTCY_YEAR_OFFSET
..record_offset + SAVE_COMPANY_RECORD_LAST_BANKRUPTCY_YEAR_OFFSET + 4]
.copy_from_slice(&last_bankruptcy_year.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_LAST_DIVIDEND_YEAR_OFFSET
..record_offset + SAVE_COMPANY_RECORD_LAST_DIVIDEND_YEAR_OFFSET + 4]
.copy_from_slice(&last_dividend_year.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET
..record_offset + SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET + 4]
.copy_from_slice(&current_issue_calendar_word.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET_2
..record_offset + SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET_2 + 4]
.copy_from_slice(&current_issue_calendar_word_2.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET
..record_offset + SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET + 4]
.copy_from_slice(&prior_issue_calendar_word.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET_2
..record_offset + SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET_2 + 4]
.copy_from_slice(&prior_issue_calendar_word_2.to_le_bytes());
bytes[record_offset + SAVE_COMPANY_RECORD_PREFERRED_LOCOMOTIVE_ENGINE_TYPE_OFFSET] =
preferred_locomotive_engine_type_raw_u8;
bytes[record_offset + SAVE_COMPANY_RECORD_CITY_CONNECTION_LATCH_OFFSET] =
u8::from(city_connection_latch);
bytes[record_offset + SAVE_COMPANY_RECORD_LINKED_TRANSIT_LATCH_OFFSET] =
u8::from(linked_transit_latch);
let current_cash: f64 = if index == 0 { 125_000.0 } else { -25_000.0 };
let current_cash_slot_offset = record_offset
+ SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET
+ (crate::RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH as usize
* crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN as usize
* 8);
bytes[current_cash_slot_offset..current_cash_slot_offset + 8]
.copy_from_slice(&current_cash.to_bits().to_le_bytes());
}
let header_probe = parse_save_company_collection_header_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
)
.expect("company header probe should parse");
let roster = parse_save_company_roster_probe(
&bytes,
Some(&header_probe),
Some(&SmpSaveWorldSelectionContextProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-direct-world-block".to_string(),
semantic_family: "scenario-selected-company-and-chairman-context".to_string(),
chunk_tag_offset: 0,
payload_offset: 0,
payload_len: 0,
payload_len_hex: "0x0".to_string(),
selected_company_id_offset: 0,
selected_company_id: 2,
selected_company_id_hex: "0x00000002".to_string(),
selected_chairman_profile_id_offset: 0,
selected_chairman_profile_id: 1,
selected_chairman_profile_id_hex: "0x00000001".to_string(),
chairman_slot_selector_offset: 0,
chairman_slot_selectors: vec![],
campaign_override_flag_offset: 0,
campaign_override_flag: 0,
campaign_override_flag_hex: "0x00".to_string(),
chairman_role_gate_offset: 0,
chairman_role_gate_bytes: vec![],
evidence: vec![],
}),
)
.expect("company roster should parse");
assert_eq!(roster.observed_entry_count, 2);
assert_eq!(roster.selected_company_id, Some(2));
assert_eq!(roster.entries.len(), 2);
assert_eq!(roster.entries[0].company_id, 1);
assert_eq!(roster.entries[0].current_cash, 125_000);
assert_eq!(roster.entries[0].linked_chairman_profile_id, Some(1));
assert_eq!(roster.entries[0].debt, 1_550_000);
assert_eq!(roster.entries[0].available_track_laying_capacity, Some(603));
assert_eq!(roster.entries[0].merger_cooldown_year, Some(1862));
let market_state = roster.entries[0]
.market_state
.as_ref()
.expect("company market state should load");
assert_eq!(market_state.outstanding_shares, 20_000);
assert_eq!(market_state.live_bond_slots.len(), 2);
assert_eq!(market_state.live_bond_slots[0].principal, 900_000);
assert_eq!(market_state.live_bond_slots[0].maturity_year, 1894);
assert_eq!(
market_state.live_bond_slots[1].coupon_rate_raw_u32,
0.12f32.to_bits()
);
assert_eq!(market_state.largest_live_bond_principal, Some(900_000));
assert_eq!(
market_state.highest_coupon_live_bond_principal,
Some(650_000)
);
assert_eq!(market_state.mutable_support_scalar_raw_u32, 0x3f800000);
assert_eq!(
market_state.young_company_support_scalar_raw_u32,
0x42340000
);
assert_eq!(market_state.support_progress_word, 17);
assert_eq!(market_state.recent_per_share_subscore_raw_u32, 0x41f00000);
assert_eq!(market_state.cached_share_price_raw_u32, 0x426c0000);
assert_eq!(market_state.chairman_salary_baseline, 24);
assert_eq!(market_state.chairman_salary_current, 31);
assert_eq!(market_state.chairman_bonus_year, 1849);
assert_eq!(market_state.chairman_bonus_amount, 1250);
assert_eq!(market_state.founding_year, 1842);
assert_eq!(market_state.last_bankruptcy_year, 1851);
assert_eq!(market_state.last_dividend_year, 1848);
assert_eq!(market_state.current_issue_calendar_word, 7);
assert_eq!(market_state.current_issue_calendar_word_2, 8);
assert_eq!(market_state.prior_issue_calendar_word, 6);
assert_eq!(market_state.prior_issue_calendar_word_2, 7);
assert_eq!(
roster.entries[0].preferred_locomotive_engine_type_raw_u8,
Some(2)
);
assert!(market_state.city_connection_latch);
assert!(!market_state.linked_transit_latch);
assert_eq!(
market_state.stat_band_root_0cfb_candidates.len(),
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS
);
assert_eq!(
market_state.stat_band_root_0d7f_candidates.len(),
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS
);
assert_eq!(
market_state.stat_band_root_1c47_candidates.len(),
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS
);
assert_eq!(
market_state.stat_band_root_0cfb_candidates[31].label,
"stat_band_0cfb_word_32"
);
assert_eq!(
market_state.stat_band_root_0cfb_candidates[31].relative_offset_hex,
"0xd77"
);
assert_eq!(roster.entries[1].company_id, 2);
assert_eq!(roster.entries[1].current_cash, -25_000);
assert_eq!(roster.entries[1].linked_chairman_profile_id, Some(2));
assert_eq!(roster.entries[1].debt, 500_000);
assert_eq!(roster.entries[1].available_track_laying_capacity, None);
assert_eq!(roster.entries[1].takeover_cooldown_year, Some(1871));
let second_market_state = roster.entries[1]
.market_state
.as_ref()
.expect("second company market state should load");
assert_eq!(
second_market_state.largest_live_bond_principal,
Some(500_000)
);
assert_eq!(
second_market_state.highest_coupon_live_bond_principal,
Some(500_000)
);
assert_eq!(second_market_state.chairman_bonus_year, 0);
assert_eq!(second_market_state.chairman_bonus_amount, 0);
assert_eq!(second_market_state.last_dividend_year, 1850);
assert_eq!(second_market_state.current_issue_calendar_word, 3);
assert_eq!(second_market_state.current_issue_calendar_word_2, 4);
assert_eq!(second_market_state.prior_issue_calendar_word, 2);
assert_eq!(second_market_state.prior_issue_calendar_word_2, 3);
assert_eq!(
roster.entries[1].preferred_locomotive_engine_type_raw_u8,
Some(1)
);
assert!(!second_market_state.city_connection_latch);
assert!(second_market_state.linked_transit_latch);
}
#[test]
fn parses_save_chairman_profile_table_probe_from_direct_records() {
let metadata_tag_offset = 0x40usize;
let stride = 0xcabusize;
let count = 2usize;
let start_offset = 0x4eusize;
let total_len = metadata_tag_offset + 4 + start_offset + stride * count + 0x200;
let mut bytes = vec![0u8; total_len];
bytes[metadata_tag_offset..metadata_tag_offset + 4]
.copy_from_slice(&0x00005209u32.to_le_bytes());
let header_words = [
1u32,
0xcab,
8,
6,
8,
count as u32,
1,
1,
0,
0,
1,
1,
1,
0,
0,
0,
0,
0,
0,
];
for (index, word) in header_words.into_iter().enumerate() {
let offset = metadata_tag_offset + 4 + index * 4;
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
}
let records_tag_offset = metadata_tag_offset + 4 + 0x100;
let close_tag_offset = records_tag_offset + 4;
bytes[records_tag_offset..records_tag_offset + 4]
.copy_from_slice(&0x0000520au32.to_le_bytes());
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
for (index, (name, linked, cash, cache0, cache1, cache4, holdings)) in [
(
"Collis Huntington",
1u32,
-107644.0f64,
252508.0f64,
0.0f64,
0.0f64,
vec![(1u32, 6000u32)],
),
(
"Thomas Durant",
2u32,
-382718.0f64,
-283562.0f64,
822000.0f64,
1_392_000.0f64,
vec![(2u32, 9000u32)],
),
]
.into_iter()
.enumerate()
{
let record_offset = metadata_tag_offset + 4 + start_offset + index * stride;
bytes[record_offset..record_offset + 4]
.copy_from_slice(&((index + 1) as u32).to_le_bytes());
bytes[record_offset + 4..record_offset + 8].copy_from_slice(&1u32.to_le_bytes());
bytes[record_offset + SAVE_CHAIRMAN_RECORD_NAME_OFFSET
..record_offset + SAVE_CHAIRMAN_RECORD_NAME_OFFSET + name.len()]
.copy_from_slice(name.as_bytes());
bytes[record_offset + SAVE_CHAIRMAN_RECORD_CASH_OFFSET
..record_offset + SAVE_CHAIRMAN_RECORD_CASH_OFFSET + 8]
.copy_from_slice(&cash.to_le_bytes());
bytes[record_offset + SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET
..record_offset + SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET + 4]
.copy_from_slice(&linked.to_le_bytes());
bytes[record_offset + SAVE_CHAIRMAN_RECORD_PERSONALITY_BYTE_0X291_OFFSET] =
(index as u8) + 10;
bytes[record_offset + SAVE_CHAIRMAN_RECORD_CACHE_0_OFFSET
..record_offset + SAVE_CHAIRMAN_RECORD_CACHE_0_OFFSET + 8]
.copy_from_slice(&cache0.to_le_bytes());
bytes[record_offset + SAVE_CHAIRMAN_RECORD_CACHE_1_OFFSET
..record_offset + SAVE_CHAIRMAN_RECORD_CACHE_1_OFFSET + 8]
.copy_from_slice(&cache1.to_le_bytes());
bytes[record_offset + 0x211..record_offset + 0x211 + 8]
.copy_from_slice(&cache4.to_le_bytes());
for (company_id, units) in holdings {
let slot_offset = record_offset
+ SAVE_CHAIRMAN_RECORD_HOLDINGS_BASE_OFFSET
+ company_id as usize * 4;
bytes[slot_offset..slot_offset + 4].copy_from_slice(&units.to_le_bytes());
}
}
let header_probe = parse_save_chairman_profile_collection_header_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
)
.expect("chairman header probe should parse");
let table = parse_save_chairman_profile_table_probe(
&bytes,
Some(&header_probe),
Some(&SmpSaveWorldSelectionContextProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-direct-world-block".to_string(),
semantic_family: "scenario-selected-company-and-chairman-context".to_string(),
chunk_tag_offset: 0,
payload_offset: 0,
payload_len: 0,
payload_len_hex: "0x0".to_string(),
selected_company_id_offset: 0,
selected_company_id: 2,
selected_company_id_hex: "0x00000002".to_string(),
selected_chairman_profile_id_offset: 0,
selected_chairman_profile_id: 2,
selected_chairman_profile_id_hex: "0x00000002".to_string(),
chairman_slot_selector_offset: 0,
chairman_slot_selectors: vec![],
campaign_override_flag_offset: 0,
campaign_override_flag: 0,
campaign_override_flag_hex: "0x00".to_string(),
chairman_role_gate_offset: 0,
chairman_role_gate_bytes: vec![],
evidence: vec![],
}),
Some(&SmpSaveTaggedCollectionHeaderProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-company-tagged-header-counts".to_string(),
semantic_family: "scenario-save-company-header-counts".to_string(),
metadata_tag_offset: 0,
records_tag_offset: 0,
close_tag_offset: 0,
direct_collection_flag: 1,
direct_collection_flag_hex: "0x00000001".to_string(),
direct_record_stride: 0x7684,
direct_record_stride_hex: "0x00007684".to_string(),
live_id_bound: 5,
live_id_bound_hex: "0x00000005".to_string(),
live_record_count: 2,
live_record_count_hex: "0x00000002".to_string(),
header_words: vec![],
header_hex_words: vec![],
evidence: vec![],
}),
)
.expect("chairman profile table should parse");
assert_eq!(table.observed_entry_count, 2);
assert_eq!(table.selected_chairman_profile_id, Some(2));
assert_eq!(table.entries.len(), 2);
assert_eq!(table.entries[0].profile_id, 1);
assert_eq!(table.entries[0].name, "Collis Huntington");
assert_eq!(table.entries[0].linked_company_id, Some(1));
assert_eq!(table.entries[0].company_holdings.get(&1), Some(&6000));
assert_eq!(table.entries[0].current_cash, -107644);
assert_eq!(table.entries[0].holdings_value_total, 252508);
assert_eq!(table.entries[0].purchasing_power_total, 144864);
assert_eq!(table.entries[0].personality_byte_0x291, Some(10));
assert_eq!(table.entries[1].profile_id, 2);
assert_eq!(table.entries[1].company_holdings.get(&2), Some(&9000));
assert_eq!(table.entries[1].holdings_value_total, 822000);
assert_eq!(table.entries[1].purchasing_power_total, 1_009_282);
assert_eq!(table.entries[1].personality_byte_0x291, Some(11));
}
#[test]
fn builds_save_world_selection_role_analysis_from_probe() {
let probe = SmpSaveWorldSelectionContextProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-direct-world-block".to_string(),
semantic_family: "scenario-selected-company-and-chairman-context".to_string(),
chunk_tag_offset: 0,
payload_offset: 0,
payload_len: 0x4f2c,
payload_len_hex: "0x4f2c".to_string(),
selected_company_id_offset: 0x21,
selected_company_id: 3,
selected_company_id_hex: "0x00000003".to_string(),
selected_chairman_profile_id_offset: 0x25,
selected_chairman_profile_id: 7,
selected_chairman_profile_id_hex: "0x00000007".to_string(),
chairman_slot_selector_offset: 0x87,
chairman_slot_selectors: vec![1, 0, 2, 0],
campaign_override_flag_offset: 0xc5,
campaign_override_flag: 1,
campaign_override_flag_hex: "0x01".to_string(),
chairman_role_gate_offset: 0x0bc3,
chairman_role_gate_bytes: vec![2, 0, 1, 0],
evidence: vec![],
};
let analysis = build_save_world_selection_role_analysis(&probe);
assert_eq!(analysis.selected_company_id, 3);
assert_eq!(analysis.selected_chairman_profile_id, 7);
assert_eq!(analysis.campaign_override_flag_hex, "0x01");
assert_eq!(analysis.chairman_slots.len(), 4);
assert_eq!(analysis.chairman_slots[0].selector_byte_hex, "0x01");
assert_eq!(analysis.chairman_slots[2].role_gate_byte_hex, "0x01");
}
#[test]
fn builds_save_candidate_views_with_raw_bits() {
let mut bytes = vec![0u8; 0x40];
bytes[0x08..0x0c].copy_from_slice(&0x3f800000u32.to_le_bytes());
bytes[0x10..0x18].copy_from_slice(&(-2458.0f64).to_le_bytes());
let dword = build_save_dword_candidate(&bytes, 0, "unit_float", 0x08)
.expect("dword candidate should build");
let qword =
build_save_qword_candidate(&bytes, 0, 0x10).expect("qword candidate should build");
assert_eq!(dword.raw_u32_hex, "0x3f800000");
assert_eq!(dword.value_i32, 1_065_353_216);
assert_eq!(dword.value_f32, 1.0);
assert_eq!(qword.raw_u64, (-2458.0f64).to_bits());
assert_eq!(qword.value_i64, (-2458.0f64).to_bits() as i64);
assert_eq!(qword.value_f64, -2458.0);
}
#[test]
fn derives_chairman_holdings_share_price_total_from_grounded_company_prices() {
let holdings_by_company =
BTreeMap::from([(2u32, 19_000u32), (4u32, 1_000u32), (6u32, 2_000u32)]);
let company_share_prices = BTreeMap::from([(2u32, 66i64), (4u32, 69i64), (6u32, 27i64)]);
let total =
derive_chairman_holdings_share_price_total(&holdings_by_company, &company_share_prices)
.expect("derived holdings total should compute");
assert_eq!(total, 1_377_000);
}
#[test]
fn derives_chairman_cached_purchasing_power_from_strongest_nonnegative_cache() {
let cached_scalar_candidates = vec![
SmpSaveScalarCandidate {
relative_offset: 0x1e9,
relative_offset_hex: "0x1e9".to_string(),
raw_u64: (-343_508.0f64).to_bits(),
raw_u64_hex: format!("0x{:016x}", (-343_508.0f64).to_bits()),
value_i64: round_f64_to_i64(-343_508.0).expect("i64"),
value_f64: -343_508.0,
},
SmpSaveScalarCandidate {
relative_offset: 0x201,
relative_offset_hex: "0x201".to_string(),
raw_u64: 1_386_000.0f64.to_bits(),
raw_u64_hex: format!("0x{:016x}", 1_386_000.0f64.to_bits()),
value_i64: round_f64_to_i64(1_386_000.0).expect("i64"),
value_f64: 1_386_000.0,
},
SmpSaveScalarCandidate {
relative_offset: 0x211,
relative_offset_hex: "0x211".to_string(),
raw_u64: 1_392_000.0f64.to_bits(),
raw_u64_hex: format!("0x{:016x}", 1_392_000.0f64.to_bits()),
value_i64: round_f64_to_i64(1_392_000.0).expect("i64"),
value_f64: 1_392_000.0,
},
];
let total =
derive_chairman_cached_purchasing_power_total(-463_436, &cached_scalar_candidates)
.expect("derived purchasing power should compute");
assert_eq!(total, 928_564);
}
#[test]
fn classifies_rt3_105_post_span_bridge_variants() {
let base_trailer = SmpRuntimeTrailerBlock {
profile_family: "rt3-105-save-container-v1".to_string(),
trailer_family: "rt3-105-save-trailer-v1".to_string(),
trailer_evidence: vec![],
trailer_offset: 944,
prefix_words_0_to_5: vec![],
prefix_hex_words_0_to_5: vec![],
tag_word_6: 0,
tag_word_6_hex: String::new(),
tag_chunk_id_u16: 0x2ee1,
tag_chunk_id_hex: "0x2ee1".to_string(),
tag_chunk_id_grounded_alignment: None,
length_word_7: 0x32c8_0000,
length_word_7_hex: "0x32c80000".to_string(),
length_high_u16: 0x32c8,
length_high_hex: "0x32c8".to_string(),
selector_word_8: 0x7110_0000,
selector_word_8_hex: "0x71100000".to_string(),
selector_high_u16: 0x7110,
selector_high_hex: "0x7110".to_string(),
layout_word_9: 0,
layout_word_9_hex: String::new(),
descriptor_word_10: 0x7801_0000,
descriptor_word_10_hex: "0x78010000".to_string(),
descriptor_high_u16: 0x7801,
descriptor_high_hex: "0x7801".to_string(),
descriptor_word_11: 0,
descriptor_word_11_hex: String::new(),
counter_word_12: 0,
counter_word_12_hex: String::new(),
offset_word_13: 0,
offset_word_13_hex: String::new(),
span_word_14: 0,
span_word_14_hex: String::new(),
mode_word_15: 0,
mode_word_15_hex: String::new(),
words: vec![],
hex_words: vec![],
};
let base_post_span = SmpRuntimePostSpanProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
span_target_offset: 13944,
next_nonzero_offset: Some(14795),
next_aligned_candidate_offset: Some(20244),
next_aligned_candidate_words: vec![],
next_aligned_candidate_hex_words: vec![],
header_candidates: vec![SmpRuntimePostSpanHeaderCandidate {
offset: 20244,
words: vec![],
hex_words: vec![],
dense_word_count: 3,
high_u16_words: vec![0x6200, 0x0000, 0xfff7, 0x5515],
high_hex_words: vec![],
grounded_alignments: vec![],
}],
grounded_progress_hits: vec![],
};
let base_profile = SmpRt3105PackedProfileProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
packed_profile_offset: 29632,
packed_profile_len: 0x108,
packed_profile_len_hex: "0x108".to_string(),
packed_profile_block: SmpRt3105PackedProfileBlock {
relative_len: 0x108,
relative_len_hex: "0x108".to_string(),
leading_word_0: 3,
leading_word_0_hex: "0x00000003".to_string(),
trailing_zero_word_count_after_leading_word: 2,
header_flag_word_3: 0x0100_0000,
header_flag_word_3_hex: "0x01000000".to_string(),
map_path_offset: 0x10,
map_path: Some("Alternate USA.gmp".to_string()),
display_name_offset: 0x43,
display_name: Some("Alternate USA".to_string()),
profile_byte_0x77: 0x07,
profile_byte_0x77_hex: "0x07".to_string(),
profile_byte_0x82: 0x4d,
profile_byte_0x82_hex: "0x4d".to_string(),
profile_byte_0x97: 0,
profile_byte_0x97_hex: "0x00".to_string(),
profile_byte_0xc5: 0,
profile_byte_0xc5_hex: "0x00".to_string(),
stable_nonzero_words: vec![],
},
ascii_runs: vec![],
};
let base_bridge = parse_rt3_105_post_span_bridge_probe(
Some(&base_trailer),
Some(&base_post_span),
Some(&base_profile),
)
.expect("base bridge should parse");
assert_eq!(
base_bridge.bridge_family,
"rt3-105-save-post-span-bridge-v1"
);
assert_eq!(base_bridge.packed_profile_delta_from_span_target, 15688);
assert_eq!(
base_bridge.next_candidate_delta_from_packed_profile,
Some(-9388)
);
let base_variant_trailer = SmpRuntimeTrailerBlock {
descriptor_word_10: 0x7401_0000,
descriptor_word_10_hex: "0x74010000".to_string(),
descriptor_high_u16: 0x7401,
descriptor_high_hex: "0x7401".to_string(),
..base_trailer.clone()
};
let base_variant_bridge = parse_rt3_105_post_span_bridge_probe(
Some(&base_variant_trailer),
Some(&base_post_span),
Some(&base_profile),
)
.expect("base bridge variant should parse");
assert_eq!(
base_variant_bridge.bridge_family,
"rt3-105-save-post-span-bridge-v1"
);
let alt_trailer = SmpRuntimeTrailerBlock {
profile_family: "rt3-105-alt-save-container-v1".to_string(),
selector_word_8: 0x54cd_0000,
selector_word_8_hex: "0x54cd0000".to_string(),
selector_high_u16: 0x54cd,
selector_high_hex: "0x54cd".to_string(),
descriptor_word_10: 0x5901_0000,
descriptor_word_10_hex: "0x59010000".to_string(),
descriptor_high_u16: 0x5901,
descriptor_high_hex: "0x5901".to_string(),
..base_trailer.clone()
};
let alt_post_span = SmpRuntimePostSpanProbe {
profile_family: "rt3-105-alt-save-container-v1".to_string(),
next_aligned_candidate_offset: Some(29892),
header_candidates: vec![SmpRuntimePostSpanHeaderCandidate {
offset: 29892,
words: vec![],
hex_words: vec![],
dense_word_count: 3,
high_u16_words: vec![0x1500, 0x0100, 0x4100, 0x0200],
high_hex_words: vec![],
grounded_alignments: vec![],
}],
..base_post_span.clone()
};
let alt_profile = SmpRt3105PackedProfileProbe {
profile_family: "rt3-105-alt-save-container-v1".to_string(),
packed_profile_block: SmpRt3105PackedProfileBlock {
map_path: Some("Spanish Mainline.gmp".to_string()),
display_name: Some("Spanish Mainline".to_string()),
profile_byte_0x82: 0xa3,
profile_byte_0x82_hex: "0xa3".to_string(),
..base_profile.packed_profile_block.clone()
},
..base_profile.clone()
};
let alt_bridge = parse_rt3_105_post_span_bridge_probe(
Some(&alt_trailer),
Some(&alt_post_span),
Some(&alt_profile),
)
.expect("alt bridge should parse");
assert_eq!(
alt_bridge.bridge_family,
"rt3-105-alt-save-post-span-bridge-v1"
);
assert_eq!(
alt_bridge.next_candidate_delta_from_packed_profile,
Some(260)
);
let scenario_trailer = SmpRuntimeTrailerBlock {
profile_family: "rt3-105-scenario-save-container-v1".to_string(),
trailer_family: "unknown".to_string(),
trailer_offset: 864,
length_word_7: 0,
length_word_7_hex: "0x00000000".to_string(),
length_high_u16: 0,
length_high_hex: "0x0000".to_string(),
selector_word_8: 0x0001_86a0,
selector_word_8_hex: "0x000186a0".to_string(),
selector_high_u16: 0x0001,
selector_high_hex: "0x0001".to_string(),
descriptor_word_10: 0x0186_a000,
descriptor_word_10_hex: "0x0186a000".to_string(),
descriptor_high_u16: 0x0186,
descriptor_high_hex: "0x0186".to_string(),
..base_trailer.clone()
};
let scenario_post_span = SmpRuntimePostSpanProbe {
profile_family: "rt3-105-scenario-save-container-v1".to_string(),
span_target_offset: 864,
next_aligned_candidate_offset: Some(940),
header_candidates: vec![SmpRuntimePostSpanHeaderCandidate {
offset: 940,
words: vec![],
hex_words: vec![],
dense_word_count: 3,
high_u16_words: vec![0x0186, 0x0006, 0x0006, 0x0001],
high_hex_words: vec![],
grounded_alignments: vec![],
}],
..base_post_span.clone()
};
let scenario_profile = SmpRt3105PackedProfileProbe {
profile_family: "rt3-105-scenario-save-container-v1".to_string(),
packed_profile_block: SmpRt3105PackedProfileBlock {
map_path: Some("Southern Pacific.gmp".to_string()),
display_name: Some("Southern Pacific".to_string()),
profile_byte_0x82: 0x90,
profile_byte_0x82_hex: "0x90".to_string(),
..base_profile.packed_profile_block.clone()
},
..base_profile.clone()
};
let scenario_bridge = parse_rt3_105_post_span_bridge_probe(
Some(&scenario_trailer),
Some(&scenario_post_span),
Some(&scenario_profile),
)
.expect("scenario bridge should parse");
assert_eq!(
scenario_bridge.bridge_family,
"rt3-105-scenario-post-span-bridge-v1"
);
assert_eq!(
scenario_bridge.next_candidate_delta_from_packed_profile,
Some(-28692)
);
}
#[test]
fn parses_rt3_105_save_bridge_payload_probe() {
let mut bytes = vec![0u8; 0x7000];
let primary = 0x4f14usize;
let secondary = 0x671cusize;
let primary_words: [u32; 8] = [
0x62000000, 0x00000000, 0xfff70000, 0x55150000, 0x55550000, 0x00000000, 0xfff70000,
0x54550000,
];
for (index, word) in primary_words.iter().enumerate() {
bytes[primary + index * 4..primary + (index + 1) * 4]
.copy_from_slice(&(*word).to_le_bytes());
}
let secondary_words: [u32; 8] = [
0x00050000, 0x00050005, 0xfff70000, 0x54540000, 0x545400f9, 0x00f900f9, 0x00f94008,
0x00001555,
];
for (index, word) in secondary_words.iter().enumerate() {
bytes[secondary + index * 4..secondary + (index + 1) * 4]
.copy_from_slice(&(*word).to_le_bytes());
}
let bridge_probe = SmpRt3105PostSpanBridgeProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
bridge_family: "rt3-105-save-post-span-bridge-v1".to_string(),
bridge_evidence: vec![],
span_target_offset: 0x3678,
next_candidate_offset: Some(primary),
next_candidate_delta_from_span_target: Some(primary - 0x3678),
packed_profile_offset: 0x73c0,
packed_profile_delta_from_span_target: 0x3d48,
next_candidate_delta_from_packed_profile: Some(primary as i64 - 0x73c0),
selector_high_u16: 0x7110,
selector_high_hex: "0x7110".to_string(),
descriptor_high_u16: 0x7801,
descriptor_high_hex: "0x7801".to_string(),
next_candidate_high_u16_words: vec![0x6200, 0x0000, 0xfff7, 0x5515],
next_candidate_high_hex_words: vec![],
};
let probe = parse_rt3_105_save_bridge_payload_probe(&bytes, Some(&bridge_probe))
.expect("save bridge payload probe should parse");
assert_eq!(probe.primary_block_offset, primary);
assert_eq!(probe.primary_block_len, 0x20);
assert_eq!(probe.secondary_block_offset, secondary);
assert_eq!(probe.secondary_block_delta_from_primary, 0x1808);
assert_eq!(probe.secondary_block_end_offset, 0x73c0);
assert_eq!(probe.secondary_block_len, 0xca4);
assert_eq!(probe.primary_words[..4], primary_words[..4]);
assert_eq!(probe.secondary_words[..8], secondary_words[..8]);
}
#[test]
fn parses_rt3_105_save_name_table_probe() {
let mut bytes = vec![0u8; 0x7400];
let secondary = 0x671cusize;
let header = secondary + 0x354;
let entries = secondary + 0x3b5;
let stride = 0x22usize;
let names = ["AluminumMill", "Nuclear Power Plant", "Bakery"];
bytes[header..header + 4].copy_from_slice(&0x10000000u32.to_le_bytes());
bytes[header + 4..header + 8].copy_from_slice(&0x00009000u32.to_le_bytes());
bytes[header + 8..header + 12].copy_from_slice(&0x0000332eu32.to_le_bytes());
bytes[header + 0x1c..header + 0x20].copy_from_slice(&4u32.to_le_bytes());
bytes[header + 0x20..header + 0x24].copy_from_slice(&(names.len() as u32).to_le_bytes());
bytes[header + 12..header + 16].copy_from_slice(&1u32.to_le_bytes());
bytes[header + 16..header + 20].copy_from_slice(&0x22u32.to_le_bytes());
bytes[header + 20..header + 24].copy_from_slice(&2u32.to_le_bytes());
bytes[header + 24..header + 28].copy_from_slice(&2u32.to_le_bytes());
bytes[header + 0x28..header + 0x2c].copy_from_slice(&1u32.to_le_bytes());
for (index, name) in names.iter().enumerate() {
let off = entries + index * stride;
let raw = &mut bytes[off..off + stride];
raw[..name.len()].copy_from_slice(name.as_bytes());
let trailer = if *name == "Nuclear Power Plant" {
0u32
} else {
1u32
};
raw[stride - 4..stride].copy_from_slice(&trailer.to_le_bytes());
}
let footer = entries + names.len() * stride;
bytes[footer..footer + 4].copy_from_slice(&0x32dcu32.to_le_bytes());
bytes[footer + 4..footer + 8].copy_from_slice(&0x3714u32.to_le_bytes());
bytes[footer + 8] = 0x00;
let payload = SmpRt3105SaveBridgePayloadProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
bridge_family: "rt3-105-save-post-span-bridge-v1".to_string(),
primary_block_offset: 0x4f14,
primary_block_len: 0x20,
primary_block_len_hex: "0x20".to_string(),
primary_words: vec![],
primary_hex_words: vec![],
secondary_block_offset: secondary,
secondary_block_delta_from_primary: 0x1808,
secondary_block_delta_from_primary_hex: "0x1808".to_string(),
secondary_block_end_offset: footer + 9,
secondary_block_len: footer + 9 - secondary,
secondary_block_len_hex: format!("0x{:x}", footer + 9 - secondary),
secondary_preview_word_count: 32,
secondary_words: vec![],
secondary_hex_words: vec![],
evidence: vec![],
};
let probe = parse_rt3_105_save_name_table_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
Some(&payload),
)
.expect("save name table probe should parse");
assert_eq!(probe.source_kind, "save-bridge-secondary-block");
assert_eq!(
probe.semantic_family,
"scenario-named-candidate-availability-table"
);
assert_eq!(probe.header_offset, header);
assert_eq!(probe.entry_stride, stride);
assert_eq!(probe.observed_entry_capacity, 4);
assert_eq!(probe.observed_entry_count, names.len());
assert_eq!(probe.entries[0].text, "AluminumMill");
assert_eq!(probe.entries[0].availability_dword, 1);
assert_eq!(probe.entries[2].text, "Bakery");
assert_eq!(probe.zero_trailer_entry_count, 1);
assert_eq!(
probe.zero_trailer_entry_names,
vec!["Nuclear Power Plant".to_string()]
);
assert_eq!(probe.trailing_footer_hex, "dc3200001437000000");
assert_eq!(probe.footer_progress_word_0, 0x32dc);
assert_eq!(probe.footer_progress_word_1, 0x3714);
assert_eq!(probe.footer_trailing_byte, 0x00);
}
#[test]
fn parses_rt3_105_map_name_table_probe_from_fixed_offsets() {
let mut bytes = vec![0u8; 0x7400];
let header = 0x6a70usize;
let entries = 0x6ad1usize;
let stride = 0x22usize;
let observed_entry_count = 67usize;
bytes[header..header + 4].copy_from_slice(&0x00000000u32.to_le_bytes());
bytes[header + 4..header + 8].copy_from_slice(&0x00000000u32.to_le_bytes());
bytes[header + 8..header + 12].copy_from_slice(&0x0000332eu32.to_le_bytes());
bytes[header + 12..header + 16].copy_from_slice(&1u32.to_le_bytes());
bytes[header + 16..header + 20].copy_from_slice(&0x22u32.to_le_bytes());
bytes[header + 20..header + 24].copy_from_slice(&2u32.to_le_bytes());
bytes[header + 24..header + 28].copy_from_slice(&2u32.to_le_bytes());
bytes[header + 0x1c..header + 0x20].copy_from_slice(&0x44u32.to_le_bytes());
bytes[header + 0x20..header + 0x24]
.copy_from_slice(&(observed_entry_count as u32).to_le_bytes());
bytes[header + 0x28..header + 0x2c].copy_from_slice(&1u32.to_le_bytes());
for index in 0..observed_entry_count {
let name = match index {
0 => "AutoPlant".to_string(),
1 => "Nuclear Power Plant".to_string(),
66 => "Warehouse11".to_string(),
_ => format!("Entry{index:02}"),
};
let off = entries + index * stride;
let raw = &mut bytes[off..off + stride];
raw[..name.len()].copy_from_slice(name.as_bytes());
let trailer = if name == "Nuclear Power Plant" {
0u32
} else {
1u32
};
raw[stride - 4..stride].copy_from_slice(&trailer.to_le_bytes());
}
let footer = entries + observed_entry_count * stride;
bytes[footer..footer + 4].copy_from_slice(&0x32dcu32.to_le_bytes());
bytes[footer + 4..footer + 8].copy_from_slice(&0x3714u32.to_le_bytes());
bytes[footer + 8] = 0x00;
let probe = parse_rt3_105_save_name_table_probe(
&bytes,
Some("gmp"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-map-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
None,
)
.expect("map name table probe should parse");
assert_eq!(probe.profile_family, "rt3-105-map-container-v1");
assert_eq!(probe.source_kind, "map-fixed-catalog-range");
assert_eq!(probe.header_offset, header);
assert_eq!(probe.entries_offset, entries);
assert_eq!(probe.observed_entry_count, observed_entry_count);
assert_eq!(probe.entries[0].text, "AutoPlant");
assert_eq!(probe.entries[66].text, "Warehouse11");
assert_eq!(
probe.zero_trailer_entry_names,
vec!["Nuclear Power Plant".to_string()]
);
assert_eq!(probe.footer_progress_word_0, 0x32dc);
assert_eq!(probe.footer_progress_word_1, 0x3714);
}
#[test]
fn parses_rt3_105_save_named_locomotive_availability_probe() {
let mut bytes = vec![0u8; 0x9000];
let packed_profile_offset = 0x73c0usize;
let packed_profile_len = 0x108usize;
let entries_offset = 0x7c78usize;
let names = [
("Eight Wheeler 4-4-0", 1u32),
("EP-2 Bipolar", 1u32),
("ET22", 1u32),
("F3", 0u32),
("Fairlie 0-6-6-0", 1u32),
("Firefly 2-2-2", 0u32),
("FP45", 0u32),
("Ge 6/6 Crocodile", 1u32),
("GG1", 0u32),
("GP7", 1u32),
];
for (index, (name, value)) in names.iter().enumerate() {
let offset = entries_offset + index * RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE;
bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
bytes[offset + 4..offset + 4 + name.len()].copy_from_slice(name.as_bytes());
}
let probe = parse_rt3_105_save_named_locomotive_availability_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
Some(&SmpRt3105PackedProfileProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
packed_profile_offset,
packed_profile_len,
packed_profile_len_hex: "0x108".to_string(),
packed_profile_block: SmpRt3105PackedProfileBlock {
relative_len: packed_profile_len,
relative_len_hex: "0x108".to_string(),
leading_word_0: 3,
leading_word_0_hex: "0x00000003".to_string(),
trailing_zero_word_count_after_leading_word: 2,
header_flag_word_3: 1,
header_flag_word_3_hex: "0x00000001".to_string(),
map_path_offset: 0x10,
map_path: Some("Alternate USA.gmp".to_string()),
display_name_offset: 0x43,
display_name: Some("Alternate USA".to_string()),
profile_byte_0x77: 0x07,
profile_byte_0x77_hex: "0x07".to_string(),
profile_byte_0x82: 0x4d,
profile_byte_0x82_hex: "0x4d".to_string(),
profile_byte_0x97: 0,
profile_byte_0x97_hex: "0x00".to_string(),
profile_byte_0xc5: 0,
profile_byte_0xc5_hex: "0x00".to_string(),
stable_nonzero_words: vec![],
},
ascii_runs: vec![],
}),
)
.expect("save-side locomotive table probe should parse");
assert_eq!(probe.source_kind, "save-direct-locomotive-row-run");
assert_eq!(
probe.semantic_family,
"scenario-named-locomotive-availability-table"
);
assert_eq!(probe.entries_offset, entries_offset);
assert_eq!(
probe.entry_stride,
RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE
);
assert_eq!(probe.observed_entry_count, names.len());
assert_eq!(probe.zero_availability_count, 4);
assert_eq!(probe.entries[0].text, "Eight Wheeler 4-4-0");
assert_eq!(probe.entries[9].text, "GP7");
}
#[test]
fn classifies_rt3_105_alt_save_container_profile() {
let shared_header = SmpSharedHeader {
byte_len: 64,
root_kind_word: 0x000025e5,
root_kind_word_hex: "0x000025e5".to_string(),
primary_family_tag: 0x00002ee0,
primary_family_tag_hex: "0x00002ee0".to_string(),
shared_signature_words_1_to_7: vec![
0x00002ee0, 0x0001c001, 0x00018000, 0x00010000, 0x00000754, 0x00000754, 0x00000754,
],
shared_signature_hex_words_1_to_7: vec![
"0x00002ee0".to_string(),
"0x0001c001".to_string(),
"0x00018000".to_string(),
"0x00010000".to_string(),
"0x00000754".to_string(),
"0x00000754".to_string(),
"0x00000754".to_string(),
],
matches_grounded_common_signature: false,
payload_window_words_8_to_9: vec![0x007a5978, 0x007a9022],
payload_window_hex_words_8_to_9: vec![
"0x007a5978".to_string(),
"0x007a9022".to_string(),
],
reserved_words_10_to_14: vec![0; 5],
reserved_words_10_to_14_all_zero: true,
final_flag_word: 0,
final_flag_word_hex: "0x00000000".to_string(),
};
let early_content_probe = SmpEarlyContentProbe {
first_post_text_nonzero_offset: 722,
zero_pad_after_text_len: 431,
first_post_text_block_len: 35,
first_post_text_block_hex:
"0101010000010000000000000100000000000000010000000000000000010100000001".to_string(),
trailing_zero_pad_after_first_block_len: 45,
secondary_nonzero_offset: Some(802),
secondary_aligned_word_window_offset: Some(800),
secondary_aligned_word_window_words: vec![
0x00010000, 0x49f00100, 0x00000002, 0xa0000000, 0x00000186, 0x00000000, 0x000186a0,
0x00000000,
],
secondary_aligned_word_window_hex_words: vec![
"0x00010000".to_string(),
"0x49f00100".to_string(),
"0x00000002".to_string(),
"0xa0000000".to_string(),
"0x00000186".to_string(),
"0x00000000".to_string(),
"0x000186a0".to_string(),
"0x00000000".to_string(),
],
secondary_preview_hex:
"01000001f04902000000000000a08601000000000000a08601000000000000a0".to_string(),
};
let header_variant = classify_header_variant_probe(&shared_header);
let secondary_variant =
classify_secondary_variant_probe(&early_content_probe).expect("secondary probe");
let container_profile = classify_container_profile(
Some("gms"),
Some(&header_variant),
Some(&secondary_variant),
)
.expect("container profile");
assert_eq!(header_variant.variant_family, "rt3-105-alt-save-header-v1");
assert_eq!(
secondary_variant.variant_family,
"rt3-105-gms-alt-family-v1"
);
assert_eq!(
container_profile.profile_family,
"rt3-105-alt-save-container-v1"
);
assert!(container_profile.is_known_profile);
}
#[test]
fn classifies_rt3_105_map_container_profiles_from_header_families() {
let scenario_profile = classify_container_profile(
Some("gmp"),
Some(&SmpHeaderVariantProbe {
variant_family: "rt3-105-scenario-save-header-v1".to_string(),
variant_evidence: vec![],
is_known_family: true,
}),
Some(&SmpSecondaryVariantProbe {
aligned_window_offset: 0,
words: vec![1, 0, 0, 0],
hex_words: vec![],
variant_family: "unknown".to_string(),
variant_evidence: vec![],
}),
)
.expect("scenario map profile");
let alt_profile = classify_container_profile(
Some("gmp"),
Some(&SmpHeaderVariantProbe {
variant_family: "rt3-105-alt-save-header-v1".to_string(),
variant_evidence: vec![],
is_known_family: true,
}),
Some(&SmpSecondaryVariantProbe {
aligned_window_offset: 0,
words: vec![0x49f00100, 2, 0xa0000000, 0x186],
hex_words: vec![],
variant_family: "unknown".to_string(),
variant_evidence: vec![],
}),
)
.expect("alt map profile");
assert_eq!(
scenario_profile.profile_family,
"rt3-105-scenario-map-container-v1"
);
assert!(scenario_profile.is_known_profile);
assert_eq!(alt_profile.profile_family, "rt3-105-alt-map-container-v1");
assert!(alt_profile.is_known_profile);
}
}