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

12629 lines
492 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, RuntimeChairmanMetric, RuntimeChairmanTarget,
RuntimeCompanyConditionTestScope, RuntimeCompanyControllerKind, 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 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 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>,
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,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 2,
label: "Company Cash",
target_mask_bits: 0x01,
parameter_family: "company_finance_scalar",
runtime_key: None,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 3,
label: "Territory - Allow All",
target_mask_bits: 0x05,
parameter_family: "territory_access_toggle",
runtime_key: None,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 8,
label: "Economic Status",
target_mask_bits: 0x08,
parameter_family: "whole_game_state_enum",
runtime_key: None,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 108,
label: "Use Wartime Cargos",
target_mask_bits: 0x08,
parameter_family: "special_condition_scalar",
runtime_key: None,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 109,
label: "Turbo Diesel Availability",
target_mask_bits: 0x08,
parameter_family: "candidate_availability_scalar",
runtime_key: None,
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"),
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 9,
label: "Confiscate All",
target_mask_bits: 0x01,
parameter_family: "company_confiscation_variant",
runtime_key: None,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 13,
label: "Deactivate Company",
target_mask_bits: 0x01,
parameter_family: "company_lifecycle_toggle",
runtime_key: None,
executable_in_runtime: true,
},
RealGroupedEffectDescriptorMetadata {
descriptor_id: 14,
label: "Deactivate Player",
target_mask_bits: 0x02,
parameter_family: "player_lifecycle_toggle",
runtime_key: None,
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,
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,
executable_in_runtime: true,
},
];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RealOrdinaryConditionMetric {
Company(RuntimeCompanyMetric),
Chairman(RuntimeChairmanMetric),
Territory(RuntimeTerritoryMetric),
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 REAL_CANDIDATE_AVAILABILITY_CONDITION_TEMPLATE_ID: i32 = 435;
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; 40] = [
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 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 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>,
}
#[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,
}
#[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, 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_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 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, Eq, 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 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 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(),
});
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,
company_roster: None,
chairman_profile_table: None,
special_conditions_table,
event_runtime_collection: report.event_runtime_collection_summary.clone(),
notes: summary.notes.clone(),
})
}
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 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 descriptor_metadata.is_none() {
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}"
));
}
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_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::Company(metric)) => {
Some(RuntimeCondition::CompanyNumericThreshold {
target: RuntimeCompanyTarget::ConditionTrueCompany,
metric,
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::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_grouped_effect_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA
.iter()
.copied()
.find(|metadata| metadata.descriptor_id == descriptor_id)
.or_else(|| recovered_cargo_production_descriptor_metadata(descriptor_id))
.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))
}
fn recovered_cargo_production_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
recovered_cargo_production_label(descriptor_id).map(|label| {
RealGroupedEffectDescriptorMetadata {
descriptor_id,
label,
target_mask_bits: 0x08,
parameter_family: "cargo_production_scalar",
runtime_key: None,
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> {
(241..=351)
.contains(&descriptor_id)
.then_some(RealGroupedEffectDescriptorMetadata {
descriptor_id,
label: "Unknown Loco Available",
target_mask_bits: 0x08,
parameter_family: "locomotive_availability_scalar",
runtime_key: None,
executable_in_runtime: false,
})
.or_else(|| {
(457..=474)
.contains(&descriptor_id)
.then_some(RealGroupedEffectDescriptorMetadata {
descriptor_id,
label: "Unknown Loco Available",
target_mask_bits: 0x08,
parameter_family: "locomotive_availability_scalar",
runtime_key: None,
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);
}
if (457..=474).contains(&descriptor_id) {
return Some(descriptor_id - 345);
}
None
}
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);
}
if (475..=500).contains(&descriptor_id) {
return Some(descriptor_id - 374);
}
None
}
fn recovered_locomotive_cost_label(descriptor_id: u32) -> Option<&'static str> {
static LABELS: OnceLock<BTreeMap<u32, &'static str>> = OnceLock::new();
LABELS
.get_or_init(|| {
(352..=451)
.chain(475..=500)
.filter_map(|descriptor_id| {
recovered_locomotive_cost_loco_id(descriptor_id).map(|loco_id| {
let label = Box::leak(format!("Locomotive {loco_id} Cost").into_boxed_str())
as &'static str;
(descriptor_id, label)
})
})
.collect()
})
.get(&descriptor_id)
.copied()
}
fn recovered_locomotive_cost_descriptor_metadata(
descriptor_id: u32,
) -> Option<RealGroupedEffectDescriptorMetadata> {
recovered_locomotive_cost_label(descriptor_id).map(|label| {
RealGroupedEffectDescriptorMetadata {
descriptor_id,
label,
target_mask_bits: 0x08,
parameter_family: "locomotive_cost_scalar",
runtime_key: None,
executable_in_runtime: false,
}
})
}
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,
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"),
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"),
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"),
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,
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,
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))
})
}
fn runtime_world_flag_key_from_label(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 derive_real_grouped_target_subject(
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
compact_control: &SmpLoadedPackedEventCompactControlSummary,
) -> Option<RealGroupedTargetSubject> {
match row.target_mask_bits {
Some(0x08) => Some(RealGroupedTargetSubject::WholeGame),
Some(0x01) => Some(RealGroupedTargetSubject::Company),
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 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.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 == "cargo_production_scalar"
&& row.row_shape == "scalar_assignment"
&& row.raw_scalar_value >= 0
{
let slot = descriptor_metadata.descriptor_id.checked_sub(229)?;
return Some(RuntimeEffect::SetCargoProductionSlot {
slot,
value: row.raw_scalar_value as u32,
});
}
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::SetLimitedTrackBuildingAmount { .. }
| RuntimeEffect::SetEconomicStatusCode { .. }
| RuntimeEffect::SetCompanyGovernanceScalar { .. }
| RuntimeEffect::SetCandidateAvailability { .. }
| RuntimeEffect::SetNamedLocomotiveAvailability { .. }
| RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. }
| RuntimeEffect::SetNamedLocomotiveCost { .. }
| RuntimeEffect::SetCargoProductionSlot { .. }
| RuntimeEffect::SetTerritoryAccessCost { .. }
| RuntimeEffect::SetSpecialCondition { .. }
| RuntimeEffect::ConfiscateCompanyAssets { .. }
| RuntimeEffect::DeactivateCompany { .. }
| RuntimeEffect::DeactivatePlayer { .. }
| RuntimeEffect::SetCompanyTrackLayingCapacity { .. }
| RuntimeEffect::RetireTrains { .. }
| RuntimeEffect::ActivateEventRecord { .. }
| RuntimeEffect::DeactivateEventRecord { .. }
| RuntimeEffect::RemoveEventRecord { .. } => true,
RuntimeEffect::SetPlayerCash { 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::AdjustCompanyCash { target, .. }
| RuntimeEffect::AdjustCompanyDebt { target, .. } => matches!(
target,
RuntimeCompanyTarget::AllActive
| RuntimeCompanyTarget::Ids { .. }
| RuntimeCompanyTarget::HumanCompanies
| RuntimeCompanyTarget::AiCompanies
| RuntimeCompanyTarget::SelectedCompany
| RuntimeCompanyTarget::ConditionTrueCompany
),
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::CompanyNumericThreshold { .. }
| RuntimeCondition::ChairmanNumericThreshold { .. }
| RuntimeCondition::TerritoryNumericThreshold { .. }
| 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 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,
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_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_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 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 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_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_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, "Unknown Loco Available");
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, "Unknown Loco Available");
assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "locomotive_availability_scalar");
assert_eq!(recovered_locomotive_availability_loco_id(457), Some(112));
}
#[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.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_lower_band_locomotive_cost_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(352).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Locomotive 1 Cost");
assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "locomotive_cost_scalar");
assert_eq!(metadata.runtime_key, None);
assert_eq!(recovered_locomotive_cost_loco_id(352), Some(1));
assert!(!metadata.executable_in_runtime);
}
#[test]
fn looks_up_recovered_upper_band_locomotive_cost_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(475).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Locomotive 101 Cost");
assert_eq!(metadata.parameter_family, "locomotive_cost_scalar");
assert_eq!(recovered_locomotive_cost_loco_id(475), Some(101));
assert!(!metadata.executable_in_runtime);
}
#[test]
fn looks_up_recovered_territory_access_cost_descriptor_metadata() {
let metadata =
real_grouped_effect_descriptor_metadata(453).expect("descriptor metadata should exist");
assert_eq!(metadata.label, "Territory Access Cost");
assert_eq!(metadata.target_mask_bits, 0x08);
assert_eq!(metadata.parameter_family, "territory_access_cost_scalar");
assert_eq!(metadata.runtime_key, None);
assert!(metadata.executable_in_runtime);
}
#[test]
fn parses_recovered_locomotive_cost_row_with_structured_locomotive_id() {
let row_bytes = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
descriptor_id: 352,
raw_scalar_value: 250000,
opcode: 3,
value_byte_0x09: 0,
value_dword_0x0d: 0,
value_byte_0x11: 0,
value_byte_0x12: 0,
value_word_0x14: 0,
value_word_0x16: 0,
locomotive_name: None,
});
let row = parse_real_grouped_effect_row_summary(&row_bytes, 0, 0, None)
.expect("row should parse");
assert_eq!(row.descriptor_id, 352);
assert_eq!(row.descriptor_label.as_deref(), Some("Locomotive 1 Cost"));
assert_eq!(row.recovered_locomotive_id, Some(1));
assert_eq!(
row.parameter_family.as_deref(),
Some("locomotive_cost_scalar")
);
}
#[test]
fn looks_up_recovered_locomotive_policy_descriptor_metadata() {
let metadata =
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 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);
}
}