33424 lines
1.5 MiB
33424 lines
1.5 MiB
use std::cmp::Reverse;
|
|
use std::collections::{BTreeMap, BTreeSet};
|
|
use std::fs;
|
|
use std::path::Path;
|
|
use std::sync::OnceLock;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
use sha2::{Digest, Sha256};
|
|
|
|
use crate::{
|
|
RuntimeCargoClass, RuntimeCargoPriceTarget, RuntimeCargoProductionTarget,
|
|
RuntimeChairmanMetric, RuntimeChairmanTarget, RuntimeCompanyConditionTestScope,
|
|
RuntimeCompanyControllerKind, RuntimeCompanyMarketState, RuntimeCompanyMetric,
|
|
RuntimeCompanyTarget, RuntimeCondition, RuntimeConditionComparator, RuntimeEffect,
|
|
RuntimeEventRecordTemplate, RuntimePlayerConditionTestScope, RuntimePlayerTarget,
|
|
RuntimeTerritoryMetric, RuntimeTerritoryTarget, RuntimeTrackMetric, RuntimeTrackPieceCounts,
|
|
};
|
|
|
|
pub const SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION: u32 = 0x03ec;
|
|
const PREAMBLE_U32_WORD_COUNT: usize = 16;
|
|
const MIN_ASCII_RUN_LEN: usize = 8;
|
|
const ASCII_PREVIEW_CHAR_LIMIT: usize = 160;
|
|
const TAG_OFFSET_SAMPLE_LIMIT: usize = 8;
|
|
const EARLY_ZERO_RUN_THRESHOLD: usize = 16;
|
|
const EARLY_PREVIEW_BYTE_LIMIT: usize = 32;
|
|
const EARLY_ALIGNED_WORD_WINDOW_COUNT: usize = 8;
|
|
const SPECIAL_CONDITIONS_OFFSET: usize = 0x0d64;
|
|
const SPECIAL_CONDITION_COUNT: usize = 36;
|
|
const SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT: usize = 35;
|
|
const SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT: usize = 50;
|
|
const SMP_ALIGNED_RUNTIME_RULE_KNOWN_EDITOR_RULE_COUNT: usize = 49;
|
|
const SMP_ALIGNED_RUNTIME_RULE_RUNTIME_OBJECT_OFFSET: usize = 0x4a7f;
|
|
const SMP_ALIGNED_RUNTIME_RULE_END_OFFSET: usize =
|
|
SPECIAL_CONDITIONS_OFFSET + SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT * 4;
|
|
const POST_SPECIAL_CONDITIONS_SCALAR_OFFSET: usize = 0x0df4;
|
|
const POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET: usize = 0x0f30;
|
|
const POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET: usize = 0x0f58;
|
|
const POST_SPECIAL_CONDITIONS_SCALAR_OVERLAP_END_OFFSET: usize =
|
|
SMP_ALIGNED_RUNTIME_RULE_END_OFFSET;
|
|
const POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET: usize = SMP_ALIGNED_RUNTIME_RULE_END_OFFSET;
|
|
const POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_OFFSET: usize =
|
|
SMP_ALIGNED_RUNTIME_RULE_RUNTIME_OBJECT_OFFSET
|
|
+ (POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET - SPECIAL_CONDITIONS_OFFSET);
|
|
const POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_END_OFFSET: usize =
|
|
SMP_ALIGNED_RUNTIME_RULE_RUNTIME_OBJECT_OFFSET
|
|
+ (POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET - SPECIAL_CONDITIONS_OFFSET);
|
|
const POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_OFFSET: usize = 0x4b47;
|
|
const POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_LEN: usize = 0x12c;
|
|
const POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_END_OFFSET: usize =
|
|
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_OFFSET
|
|
+ POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_LEN;
|
|
const POST_TEXT_FIELD_NEIGHBORHOOD_OFFSET: usize = 0x0f59;
|
|
const POST_TEXT_FIELD_NEIGHBORHOOD_END_OFFSET: usize = 0x0f75;
|
|
const POST_TEXT_FIELD_0_RUNTIME_OBJECT_OFFSET: usize = 0x4c74;
|
|
const POST_TEXT_FIELD_1_RUNTIME_OBJECT_OFFSET: usize = 0x4c78;
|
|
const POST_TEXT_FIELD_2_RUNTIME_OBJECT_OFFSET: usize = 0x4c7c;
|
|
const POST_TEXT_FIELD_3_RUNTIME_OBJECT_OFFSET: usize = 0x4c80;
|
|
const POST_TEXT_FIELD_4_RUNTIME_OBJECT_OFFSET: usize = 0x4c88;
|
|
const POST_TEXT_FIELD_5_RUNTIME_OBJECT_OFFSET: usize = 0x4c8c;
|
|
const POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_OFFSET: usize = 0x4c80;
|
|
const POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_OFFSET: usize = 0x4c8c;
|
|
const POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_FILE_OFFSET: usize =
|
|
SPECIAL_CONDITIONS_OFFSET
|
|
+ (POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_OFFSET
|
|
- SMP_ALIGNED_RUNTIME_RULE_RUNTIME_OBJECT_OFFSET);
|
|
const POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_FILE_OFFSET: usize =
|
|
SPECIAL_CONDITIONS_OFFSET
|
|
+ (POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_OFFSET
|
|
- SMP_ALIGNED_RUNTIME_RULE_RUNTIME_OBJECT_OFFSET);
|
|
const LOCOMOTIVE_POLICY_NEIGHBORHOOD_OFFSET: usize = 0x0f78;
|
|
const LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET: usize = 0x0fa7;
|
|
const LOCOMOTIVE_POLICY_FIELD_NEG3_RUNTIME_OBJECT_OFFSET: usize = 0x4ca2;
|
|
const LOCOMOTIVE_POLICY_FIELD_NEG2_RUNTIME_OBJECT_OFFSET: usize = 0x4cae;
|
|
const LOCOMOTIVE_POLICY_FIELD_NEG1_RUNTIME_OBJECT_OFFSET: usize = 0x4cb2;
|
|
const LOCOMOTIVE_POLICY_FIELD_0_RUNTIME_OBJECT_OFFSET: usize = 0x4c93;
|
|
const LOCOMOTIVE_POLICY_FIELD_1_RUNTIME_OBJECT_OFFSET: usize = 0x4c97;
|
|
const LOCOMOTIVE_POLICY_FIELD_2_RUNTIME_OBJECT_OFFSET: usize = 0x4c98;
|
|
const LOCOMOTIVE_POLICY_FIELD_3_RUNTIME_OBJECT_OFFSET: usize = 0x4c99;
|
|
const LOCOMOTIVE_POLICY_FIELD_4_RUNTIME_OBJECT_OFFSET: usize = 0x4cba;
|
|
const LOCOMOTIVE_POLICY_FIELD_5_RUNTIME_OBJECT_OFFSET: usize = 0x4cbe;
|
|
const PRE_RECIPE_SCALAR_PLATEAU_OFFSET: usize = 0x0fa7;
|
|
const PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET: usize = 0x0fe7;
|
|
const RECIPE_BOOK_ROOT_OFFSET: usize = 0x0fe7;
|
|
const RECIPE_BOOK_COUNT: usize = 12;
|
|
const RECIPE_BOOK_STRIDE: usize = 0x4e1;
|
|
const RECIPE_BOOK_HEAD_SAMPLE_LEN: usize = 16;
|
|
const RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET: usize = 0x3ed;
|
|
const RECIPE_BOOK_LINE_AREA_OFFSET: usize = 0x3f1;
|
|
const RECIPE_BOOK_LINE_COUNT: usize = 5;
|
|
const RECIPE_BOOK_LINE_STRIDE: usize = 0x30;
|
|
const RECIPE_BOOK_LINE_AREA_LEN: usize = RECIPE_BOOK_LINE_COUNT * RECIPE_BOOK_LINE_STRIDE;
|
|
const RECIPE_BOOK_SUMMARY_END_OFFSET: usize =
|
|
RECIPE_BOOK_ROOT_OFFSET + RECIPE_BOOK_COUNT * RECIPE_BOOK_STRIDE;
|
|
const RT3_SAVE_WORLD_BLOCK_CHUNK_TAG: u32 = 0x000032c8;
|
|
const RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG: u32 = 0x000032c9;
|
|
const RT3_SAVE_WORLD_BLOCK_LEN: usize = 0x4f2c;
|
|
const RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET: usize = 0x1d;
|
|
const RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET: usize = 0x21;
|
|
const RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_MULTIPLIER_RELATIVE_OFFSET: usize = 0x25;
|
|
const RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET: usize = 0x29;
|
|
const RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET: usize = 0x0d;
|
|
const RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_2_RELATIVE_OFFSET: usize = 0x11;
|
|
const RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_RELATIVE_OFFSET: usize = 0x15;
|
|
const RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_MIRROR_RELATIVE_OFFSET: usize = 0x19;
|
|
const RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_CANDIDATE_FIELDS: [(&str, usize); 11] = [
|
|
(
|
|
"current_calendar_tuple_word",
|
|
RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET,
|
|
),
|
|
(
|
|
"current_calendar_tuple_word_2",
|
|
RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_2_RELATIVE_OFFSET,
|
|
),
|
|
(
|
|
"absolute_calendar_counter",
|
|
RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_RELATIVE_OFFSET,
|
|
),
|
|
(
|
|
"absolute_calendar_counter_mirror",
|
|
RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_MIRROR_RELATIVE_OFFSET,
|
|
),
|
|
("selection_context_candidate_0", 0x1d),
|
|
("selection_context_candidate_1", 0x21),
|
|
("issue_0x37_multiplier", 0x25),
|
|
("issue_0x37_value", 0x29),
|
|
("issue_neighbor_candidate_0", 0x2d),
|
|
("issue_neighbor_candidate_1", 0x31),
|
|
("issue_neighbor_candidate_2", 0x35),
|
|
];
|
|
const RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_ROOT_RELATIVE_OFFSET: usize = 0x0d;
|
|
const RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_WINDOW_LEN_DWORDS: usize = 17;
|
|
const RT3_SAVE_WORLD_BLOCK_ISSUE_OPINION_BASE_TERMS_OFFSET: usize = 0x8a;
|
|
const RT3_SAVE_WORLD_BLOCK_ISSUE_OPINION_TERM_COUNT: usize = 0x3b;
|
|
const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_SELECTOR_RELATIVE_OFFSET: usize = 0x83;
|
|
const RT3_SAVE_WORLD_BLOCK_CAMPAIGN_OVERRIDE_FLAG_RELATIVE_OFFSET: usize = 0xc1;
|
|
const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_RELATIVE_OFFSET: usize = 0x0bbf;
|
|
const RT3_SAVE_WORLD_BLOCK_STOCK_POLICY_RELATIVE_OFFSET: usize = 0x4a83;
|
|
const RT3_SAVE_WORLD_BLOCK_BOND_POLICY_RELATIVE_OFFSET: usize = 0x4a87;
|
|
const RT3_SAVE_WORLD_BLOCK_BANKRUPTCY_POLICY_RELATIVE_OFFSET: usize = 0x4a8b;
|
|
const RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET: usize = 0x4a8f;
|
|
const RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET: usize = 0x4c78;
|
|
const RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET: usize = 0x0bda;
|
|
const RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_PRIMARY_RELATIVE_OFFSETS: [usize; 6] =
|
|
[0x0bde, 0x0be2, 0x0be6, 0x0bea, 0x0bee, 0x0bf2];
|
|
const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_COUNT: usize = 16;
|
|
const RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_STRIDE: usize = 9;
|
|
const EVENT_RUNTIME_COLLECTION_METADATA_TAG: u16 = 0x4e99;
|
|
const EVENT_RUNTIME_COLLECTION_RECORDS_TAG: u16 = 0x4e9a;
|
|
const EVENT_RUNTIME_COLLECTION_CLOSE_TAG: u16 = 0x4e9b;
|
|
const EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION: u32 = 0x000003e9;
|
|
const INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT: usize = 19;
|
|
const INDEXED_COLLECTION_SERIALIZED_HEADER_LEN: usize =
|
|
INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT * 4;
|
|
const SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX: usize = 16;
|
|
const SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT: usize = 3;
|
|
const SAVE_REGION_RECORD_NAME_TAG: u16 = 0x55f1;
|
|
const SAVE_REGION_RECORD_POLICY_TAG: u16 = 0x55f2;
|
|
const SAVE_REGION_RECORD_PROFILE_TAG: u16 = 0x55f3;
|
|
const SAVE_REGION_FIXED_ROW_STRIDE: usize = 0x29;
|
|
const SAVE_REGION_FIXED_ROW_DWORD_LANE_COUNT: usize = SAVE_REGION_FIXED_ROW_STRIDE / 4;
|
|
const SAVE_REGION_FIXED_ROW_CANDIDATE_PROBE_LIMIT: usize = 24;
|
|
const SAVE_REGION_QUEUED_NOTICE_PAYLOAD_SEED: u32 = 0x005c87a8;
|
|
const SAVE_REGION_QUEUED_NOTICE_NODE_KIND: u32 = 7;
|
|
const SAVE_REGION_QUEUED_NOTICE_NODE_LEN: usize = 0x20;
|
|
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_RECORD_TERMINATOR_MARKER: u16 = 0x4eb9;
|
|
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_NONDIRECT_CONDITION_ROW_SERIALIZED_LEN: usize = 22;
|
|
const PACKED_EVENT_NONDIRECT_GROUPED_EFFECT_ROW_SERIALIZED_LEN: usize = 45;
|
|
const PACKED_EVENT_NONDIRECT_OPTIONAL_NAME_BLOCK_LEN: usize = 0x64;
|
|
const PACKED_EVENT_TEXT_BAND_LABELS: [&str; 6] = [
|
|
"primary_text_band",
|
|
"secondary_text_band_0",
|
|
"secondary_text_band_1",
|
|
"secondary_text_band_2",
|
|
"secondary_text_band_3",
|
|
"secondary_text_band_4",
|
|
];
|
|
const SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX: usize =
|
|
(POST_SPECIAL_CONDITIONS_SCALAR_OFFSET - SPECIAL_CONDITIONS_OFFSET) / 4;
|
|
const SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT: usize =
|
|
(SMP_ALIGNED_RUNTIME_RULE_END_OFFSET - POST_SPECIAL_CONDITIONS_SCALAR_OFFSET) / 4;
|
|
const SHARED_SIGNATURE_WORDS_1_TO_7: [u32; 7] = [
|
|
0x00002ee0, 0x00040001, 0x00028000, 0x00010000, 0x00000771, 0x00000771, 0x00000771,
|
|
];
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
struct RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id: u32,
|
|
label: &'static str,
|
|
target_mask_bits: u8,
|
|
parameter_family: &'static str,
|
|
runtime_key: Option<&'static str>,
|
|
runtime_status: RealGroupedEffectRuntimeStatus,
|
|
executable_in_runtime: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
enum RealGroupedEffectRuntimeStatus {
|
|
Executable,
|
|
ShellOwned,
|
|
EvidenceBlocked,
|
|
VariantOrScopeBlocked,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
|
struct CheckedInEventEffectsSemanticCatalogArtifact {
|
|
descriptors: Vec<CheckedInEventEffectSemanticRow>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Deserialize)]
|
|
struct CheckedInEventEffectSemanticRow {
|
|
descriptor_id: u32,
|
|
label: String,
|
|
target_mask_bits: u8,
|
|
parameter_family: String,
|
|
runtime_key: Option<String>,
|
|
runtime_status: String,
|
|
executable_in_runtime: bool,
|
|
}
|
|
|
|
const REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA: [RealGroupedEffectDescriptorMetadata; 12] = [
|
|
RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id: 1,
|
|
label: "Player Cash",
|
|
target_mask_bits: 0x02,
|
|
parameter_family: "player_finance_scalar",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
},
|
|
RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id: 2,
|
|
label: "Company Cash",
|
|
target_mask_bits: 0x01,
|
|
parameter_family: "company_finance_scalar",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
},
|
|
RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id: 3,
|
|
label: "Territory - Allow All",
|
|
target_mask_bits: 0x05,
|
|
parameter_family: "territory_access_toggle",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
},
|
|
RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id: 8,
|
|
label: "Economic Status",
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "whole_game_state_enum",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
},
|
|
RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id: 108,
|
|
label: "Use Wartime Cargos",
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "special_condition_scalar",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
},
|
|
RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id: 109,
|
|
label: "Turbo Diesel Availability",
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "candidate_availability_scalar",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
},
|
|
RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id: 110,
|
|
label: "Disable Stock Buying and Selling",
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "world_flag_toggle",
|
|
runtime_key: Some("world.disable_stock_buying_and_selling"),
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
},
|
|
RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id: 9,
|
|
label: "Confiscate All",
|
|
target_mask_bits: 0x01,
|
|
parameter_family: "company_confiscation_variant",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
},
|
|
RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id: 13,
|
|
label: "Deactivate Company",
|
|
target_mask_bits: 0x01,
|
|
parameter_family: "company_lifecycle_toggle",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
},
|
|
RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id: 14,
|
|
label: "Deactivate Player",
|
|
target_mask_bits: 0x02,
|
|
parameter_family: "player_lifecycle_toggle",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
},
|
|
RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id: 15,
|
|
label: "Retire Train",
|
|
target_mask_bits: 0x0d,
|
|
parameter_family: "company_or_territory_asset_toggle",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
},
|
|
RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id: 16,
|
|
label: "Company Track Pieces Buildable",
|
|
target_mask_bits: 0x01,
|
|
parameter_family: "company_build_limit_scalar",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
},
|
|
];
|
|
|
|
fn real_grouped_effect_runtime_status_name(status: RealGroupedEffectRuntimeStatus) -> &'static str {
|
|
match status {
|
|
RealGroupedEffectRuntimeStatus::Executable => "executable",
|
|
RealGroupedEffectRuntimeStatus::ShellOwned => "shell_owned",
|
|
RealGroupedEffectRuntimeStatus::EvidenceBlocked => "evidence_blocked",
|
|
RealGroupedEffectRuntimeStatus::VariantOrScopeBlocked => "variant_or_scope_blocked",
|
|
}
|
|
}
|
|
|
|
fn checked_in_event_effect_descriptor_rows()
|
|
-> &'static BTreeMap<u32, RealGroupedEffectDescriptorMetadata> {
|
|
static ROWS: OnceLock<BTreeMap<u32, RealGroupedEffectDescriptorMetadata>> = OnceLock::new();
|
|
ROWS.get_or_init(|| {
|
|
let artifact: CheckedInEventEffectsSemanticCatalogArtifact = serde_json::from_str(
|
|
include_str!("../../../artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json"),
|
|
)
|
|
.expect("checked-in event-effects semantic catalog should parse");
|
|
artifact
|
|
.descriptors
|
|
.into_iter()
|
|
.map(|row| {
|
|
(
|
|
row.descriptor_id,
|
|
checked_in_event_effect_descriptor_metadata(row),
|
|
)
|
|
})
|
|
.collect()
|
|
})
|
|
}
|
|
|
|
fn checked_in_event_effect_descriptor_metadata(
|
|
row: CheckedInEventEffectSemanticRow,
|
|
) -> RealGroupedEffectDescriptorMetadata {
|
|
let label = Box::leak(row.label.clone().into_boxed_str()) as &'static str;
|
|
let parameter_family = Box::leak(row.parameter_family.into_boxed_str()) as &'static str;
|
|
let runtime_key = row
|
|
.runtime_key
|
|
.map(|key| Box::leak(key.into_boxed_str()) as &'static str);
|
|
let runtime_status = match row.runtime_status.as_str() {
|
|
"executable" => RealGroupedEffectRuntimeStatus::Executable,
|
|
"shell_owned" => RealGroupedEffectRuntimeStatus::ShellOwned,
|
|
"evidence_blocked" => RealGroupedEffectRuntimeStatus::EvidenceBlocked,
|
|
"variant_or_scope_blocked" => RealGroupedEffectRuntimeStatus::VariantOrScopeBlocked,
|
|
other => panic!("unknown checked-in event-effect runtime status {other}"),
|
|
};
|
|
RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id: row.descriptor_id,
|
|
label,
|
|
target_mask_bits: row.target_mask_bits,
|
|
parameter_family,
|
|
runtime_key,
|
|
runtime_status,
|
|
executable_in_runtime: row.executable_in_runtime,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn grouped_effect_descriptor_runtime_status_name(
|
|
descriptor_id: u32,
|
|
) -> Option<&'static str> {
|
|
real_grouped_effect_descriptor_metadata(descriptor_id)
|
|
.map(|metadata| real_grouped_effect_runtime_status_name(metadata.runtime_status))
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
enum RealOrdinaryConditionMetric {
|
|
WorldVariable(u32),
|
|
Company(RuntimeCompanyMetric),
|
|
CompanyVariable(u32),
|
|
PlayerVariable(u32),
|
|
Chairman(RuntimeChairmanMetric),
|
|
Territory(RuntimeTerritoryMetric),
|
|
TerritoryVariable(u32),
|
|
CompanyTerritory(RuntimeTrackMetric),
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
enum RealWorldConditionKind {
|
|
SpecialCondition { label: &'static str },
|
|
CandidateAvailability,
|
|
NamedLocomotiveAvailability,
|
|
NamedLocomotiveCost,
|
|
CargoProductionSlot,
|
|
CargoProductionTotal,
|
|
FactoryProductionTotal,
|
|
FarmMineProductionTotal,
|
|
OtherCargoProductionTotal,
|
|
LimitedTrackBuildingAmount,
|
|
TerritoryAccessCost,
|
|
EconomicStatus,
|
|
WorldFlag { key: &'static str },
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
enum RealOrdinaryConditionKind {
|
|
Numeric(RealOrdinaryConditionMetric),
|
|
WorldState(RealWorldConditionKind),
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
struct RealOrdinaryConditionMetadata {
|
|
raw_condition_id: i32,
|
|
label: &'static str,
|
|
kind: RealOrdinaryConditionKind,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
struct KnownCargoSlotDefinition {
|
|
slot_id: u32,
|
|
label: &'static str,
|
|
cargo_class: RuntimeCargoClass,
|
|
descriptor_id: u32,
|
|
}
|
|
|
|
const KNOWN_CARGO_SLOT_DEFINITIONS: [KnownCargoSlotDefinition; 11] = [
|
|
KnownCargoSlotDefinition {
|
|
slot_id: 1,
|
|
label: "Cargo Production Slot 1",
|
|
cargo_class: RuntimeCargoClass::Factory,
|
|
descriptor_id: 230,
|
|
},
|
|
KnownCargoSlotDefinition {
|
|
slot_id: 2,
|
|
label: "Cargo Production Slot 2",
|
|
cargo_class: RuntimeCargoClass::Factory,
|
|
descriptor_id: 231,
|
|
},
|
|
KnownCargoSlotDefinition {
|
|
slot_id: 3,
|
|
label: "Cargo Production Slot 3",
|
|
cargo_class: RuntimeCargoClass::Factory,
|
|
descriptor_id: 232,
|
|
},
|
|
KnownCargoSlotDefinition {
|
|
slot_id: 4,
|
|
label: "Cargo Production Slot 4",
|
|
cargo_class: RuntimeCargoClass::Factory,
|
|
descriptor_id: 233,
|
|
},
|
|
KnownCargoSlotDefinition {
|
|
slot_id: 5,
|
|
label: "Cargo Production Slot 5",
|
|
cargo_class: RuntimeCargoClass::FarmMine,
|
|
descriptor_id: 234,
|
|
},
|
|
KnownCargoSlotDefinition {
|
|
slot_id: 6,
|
|
label: "Cargo Production Slot 6",
|
|
cargo_class: RuntimeCargoClass::FarmMine,
|
|
descriptor_id: 235,
|
|
},
|
|
KnownCargoSlotDefinition {
|
|
slot_id: 7,
|
|
label: "Cargo Production Slot 7",
|
|
cargo_class: RuntimeCargoClass::FarmMine,
|
|
descriptor_id: 236,
|
|
},
|
|
KnownCargoSlotDefinition {
|
|
slot_id: 8,
|
|
label: "Cargo Production Slot 8",
|
|
cargo_class: RuntimeCargoClass::FarmMine,
|
|
descriptor_id: 237,
|
|
},
|
|
KnownCargoSlotDefinition {
|
|
slot_id: 9,
|
|
label: "Cargo Production Slot 9",
|
|
cargo_class: RuntimeCargoClass::Other,
|
|
descriptor_id: 238,
|
|
},
|
|
KnownCargoSlotDefinition {
|
|
slot_id: 10,
|
|
label: "Cargo Production Slot 10",
|
|
cargo_class: RuntimeCargoClass::Other,
|
|
descriptor_id: 239,
|
|
},
|
|
KnownCargoSlotDefinition {
|
|
slot_id: 11,
|
|
label: "Cargo Production Slot 11",
|
|
cargo_class: RuntimeCargoClass::Other,
|
|
descriptor_id: 240,
|
|
},
|
|
];
|
|
|
|
const GROUNDED_LOCOMOTIVE_PREFIX: [&str; 61] = [
|
|
"2-D-2",
|
|
"E-88",
|
|
"Adler 2-2-2",
|
|
"USA 103",
|
|
"American 4-4-0",
|
|
"Atlantic 4-4-2",
|
|
"Baldwin 0-6-0",
|
|
"Be 5/7",
|
|
"Beuth 2-2-2",
|
|
"Big Boy 4-8-8-4",
|
|
"C55 Deltic",
|
|
"Camelback 0-6-0",
|
|
"Challenger 4-6-6-4",
|
|
"Class 01 4-6-2",
|
|
"Class 103",
|
|
"Class 132",
|
|
"Class 500 4-6-0",
|
|
"Class 9100",
|
|
"Class EF 66",
|
|
"Class 6E",
|
|
"Consolidation 2-8-0",
|
|
"Crampton 4-2-0",
|
|
"DD 080-X",
|
|
"DD40AX",
|
|
"Duke Class 4-4-0",
|
|
"E18",
|
|
"E428",
|
|
"Brenner E412",
|
|
"E60CP",
|
|
"Eight Wheeler 4-4-0",
|
|
"EP-2 Bipolar",
|
|
"ET22",
|
|
"F3",
|
|
"Fairlie 0-6-6-0",
|
|
"Firefly 2-2-2",
|
|
"FP45",
|
|
"Ge 6/6 Crocodile",
|
|
"GG1",
|
|
"GP7",
|
|
"H10 2-8-2",
|
|
"HST 125",
|
|
"Kriegslok 2-10-0",
|
|
"Mallard 4-6-2",
|
|
"Norris 4-2-0",
|
|
"Northern 4-8-4",
|
|
"Orca NX462",
|
|
"Pacific 4-6-2",
|
|
"Planet 2-2-0",
|
|
"Re 6/6",
|
|
"Red Devil 4-8-4",
|
|
"S3 4-4-0",
|
|
"NA-90D",
|
|
"Shay (2-Truck)",
|
|
"Shinkansen Series 0",
|
|
"Stirling 4-2-2",
|
|
"Trans-Euro",
|
|
"V200",
|
|
"VL80T",
|
|
"GP 35",
|
|
"U1",
|
|
"Zephyr",
|
|
];
|
|
|
|
const REAL_CANDIDATE_AVAILABILITY_CONDITION_TEMPLATE_ID: i32 = 435;
|
|
const REAL_WORLD_VARIABLE_1_CONDITION_ID: i32 = 2241;
|
|
const REAL_WORLD_VARIABLE_2_CONDITION_ID: i32 = 2242;
|
|
const REAL_WORLD_VARIABLE_3_CONDITION_ID: i32 = 2243;
|
|
const REAL_WORLD_VARIABLE_4_CONDITION_ID: i32 = 2244;
|
|
const REAL_COMPANY_VARIABLE_1_CONDITION_ID: i32 = 2245;
|
|
const REAL_COMPANY_VARIABLE_2_CONDITION_ID: i32 = 2246;
|
|
const REAL_COMPANY_VARIABLE_3_CONDITION_ID: i32 = 2247;
|
|
const REAL_COMPANY_VARIABLE_4_CONDITION_ID: i32 = 2248;
|
|
const REAL_PLAYER_VARIABLE_1_CONDITION_ID: i32 = 2249;
|
|
const REAL_PLAYER_VARIABLE_2_CONDITION_ID: i32 = 2250;
|
|
const REAL_PLAYER_VARIABLE_3_CONDITION_ID: i32 = 2251;
|
|
const REAL_PLAYER_VARIABLE_4_CONDITION_ID: i32 = 2252;
|
|
const REAL_TERRITORY_VARIABLE_1_CONDITION_ID: i32 = 2253;
|
|
const REAL_TERRITORY_VARIABLE_2_CONDITION_ID: i32 = 2254;
|
|
const REAL_TERRITORY_VARIABLE_3_CONDITION_ID: i32 = 2255;
|
|
const REAL_TERRITORY_VARIABLE_4_CONDITION_ID: i32 = 2256;
|
|
const REAL_CHAIRMAN_CASH_CONDITION_ID: i32 = 2218;
|
|
const REAL_CHAIRMAN_HOLDINGS_TOTAL_CONDITION_ID: i32 = 2239;
|
|
const REAL_CHAIRMAN_NET_WORTH_CONDITION_ID: i32 = 2240;
|
|
const REAL_CHAIRMAN_PURCHASING_POWER_CONDITION_ID: i32 = 1247;
|
|
const REAL_INVESTOR_CONFIDENCE_CONDITION_ID: i32 = 2366;
|
|
const REAL_CREDIT_RATING_CONDITION_ID: i32 = 2367;
|
|
const REAL_PRIME_RATE_CONDITION_ID: i32 = 2368;
|
|
const REAL_MANAGEMENT_ATTITUDE_CONDITION_ID: i32 = 2369;
|
|
const REAL_BOOK_VALUE_PER_SHARE_CONDITION_ID: i32 = 2620;
|
|
const REAL_CARGO_PRODUCTION_CONDITION_TEMPLATE_ID: i32 = 200;
|
|
const REAL_NAMED_LOCOMOTIVE_AVAILABILITY_CONDITION_ID: i32 = 2422;
|
|
const REAL_NAMED_LOCOMOTIVE_COST_CONDITION_ID: i32 = 2423;
|
|
const REAL_CARGO_PRODUCTION_TOTAL_CONDITION_ID: i32 = 2418;
|
|
const REAL_FACTORY_PRODUCTION_TOTAL_CONDITION_ID: i32 = 2419;
|
|
const REAL_FARM_MINE_PRODUCTION_TOTAL_CONDITION_ID: i32 = 2420;
|
|
const REAL_OTHER_CARGO_PRODUCTION_TOTAL_CONDITION_ID: i32 = 2421;
|
|
const REAL_LIMITED_TRACK_BUILDING_AMOUNT_CONDITION_ID: i32 = 2547;
|
|
const REAL_TERRITORY_ACCESS_COST_CONDITION_ID: i32 = 1516;
|
|
|
|
const REAL_ORDINARY_CONDITION_METADATA: [RealOrdinaryConditionMetadata; 56] = [
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_WORLD_VARIABLE_1_CONDITION_ID,
|
|
label: "Game Variable 1",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(1)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_WORLD_VARIABLE_2_CONDITION_ID,
|
|
label: "Game Variable 2",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(2)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_WORLD_VARIABLE_3_CONDITION_ID,
|
|
label: "Game Variable 3",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(3)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_WORLD_VARIABLE_4_CONDITION_ID,
|
|
label: "Game Variable 4",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(4)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_COMPANY_VARIABLE_1_CONDITION_ID,
|
|
label: "Company Variable 1",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(1)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_COMPANY_VARIABLE_2_CONDITION_ID,
|
|
label: "Company Variable 2",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(2)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_COMPANY_VARIABLE_3_CONDITION_ID,
|
|
label: "Company Variable 3",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(3)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_COMPANY_VARIABLE_4_CONDITION_ID,
|
|
label: "Company Variable 4",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(4)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_PLAYER_VARIABLE_1_CONDITION_ID,
|
|
label: "Player Variable 1",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(1)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_PLAYER_VARIABLE_2_CONDITION_ID,
|
|
label: "Player Variable 2",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(2)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_PLAYER_VARIABLE_3_CONDITION_ID,
|
|
label: "Player Variable 3",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(3)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_PLAYER_VARIABLE_4_CONDITION_ID,
|
|
label: "Player Variable 4",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(4)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_TERRITORY_VARIABLE_1_CONDITION_ID,
|
|
label: "Territory Variable 1",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(1)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_TERRITORY_VARIABLE_2_CONDITION_ID,
|
|
label: "Territory Variable 2",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(2)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_TERRITORY_VARIABLE_3_CONDITION_ID,
|
|
label: "Territory Variable 3",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(3)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_TERRITORY_VARIABLE_4_CONDITION_ID,
|
|
label: "Territory Variable 4",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(4)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 1802,
|
|
label: "Current Cash",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
|
RuntimeCompanyMetric::CurrentCash,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_CHAIRMAN_CASH_CONDITION_ID,
|
|
label: "Player Cash",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(
|
|
RuntimeChairmanMetric::CurrentCash,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_CHAIRMAN_HOLDINGS_TOTAL_CONDITION_ID,
|
|
label: "Player Stock Value",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(
|
|
RuntimeChairmanMetric::HoldingsValueTotal,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_CHAIRMAN_NET_WORTH_CONDITION_ID,
|
|
label: "Player Net Worth",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(
|
|
RuntimeChairmanMetric::NetWorthTotal,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_CHAIRMAN_PURCHASING_POWER_CONDITION_ID,
|
|
label: "Purchasing Power",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(
|
|
RuntimeChairmanMetric::PurchasingPowerTotal,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 951,
|
|
label: "Total Debt",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
|
RuntimeCompanyMetric::TotalDebt,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_INVESTOR_CONFIDENCE_CONDITION_ID,
|
|
label: "Investor Confidence",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
|
RuntimeCompanyMetric::InvestorConfidence,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_CREDIT_RATING_CONDITION_ID,
|
|
label: "Credit Rating",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
|
RuntimeCompanyMetric::CreditRating,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_PRIME_RATE_CONDITION_ID,
|
|
label: "Prime Rate",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
|
RuntimeCompanyMetric::PrimeRate,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_MANAGEMENT_ATTITUDE_CONDITION_ID,
|
|
label: "Management Attitude",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
|
RuntimeCompanyMetric::ManagementAttitude,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_BOOK_VALUE_PER_SHARE_CONDITION_ID,
|
|
label: "Book Value Per Share",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
|
RuntimeCompanyMetric::BookValuePerShare,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2293,
|
|
label: "Company Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
|
RuntimeCompanyMetric::TrackPiecesTotal,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2294,
|
|
label: "Company Single Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
|
RuntimeCompanyMetric::TrackPiecesSingle,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2295,
|
|
label: "Company Double Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
|
RuntimeCompanyMetric::TrackPiecesDouble,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2296,
|
|
label: "Company Transition Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
|
RuntimeCompanyMetric::TrackPiecesTransition,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2297,
|
|
label: "Company Electric Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
|
RuntimeCompanyMetric::TrackPiecesElectric,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2298,
|
|
label: "Company Non-Electric Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(
|
|
RuntimeCompanyMetric::TrackPiecesNonElectric,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2313,
|
|
label: "Territory Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
|
|
RuntimeTerritoryMetric::TrackPiecesTotal,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2314,
|
|
label: "Territory Single Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
|
|
RuntimeTerritoryMetric::TrackPiecesSingle,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2315,
|
|
label: "Territory Double Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
|
|
RuntimeTerritoryMetric::TrackPiecesDouble,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2316,
|
|
label: "Territory Transition Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
|
|
RuntimeTerritoryMetric::TrackPiecesTransition,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2317,
|
|
label: "Territory Electric Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
|
|
RuntimeTerritoryMetric::TrackPiecesElectric,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2318,
|
|
label: "Territory Non-Electric Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(
|
|
RuntimeTerritoryMetric::TrackPiecesNonElectric,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2323,
|
|
label: "Company-Territory Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
|
|
RuntimeTrackMetric::Total,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2324,
|
|
label: "Company-Territory Single Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
|
|
RuntimeTrackMetric::Single,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2325,
|
|
label: "Company-Territory Double Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
|
|
RuntimeTrackMetric::Double,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2326,
|
|
label: "Company-Territory Transition Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
|
|
RuntimeTrackMetric::Transition,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2327,
|
|
label: "Company-Territory Electric Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
|
|
RuntimeTrackMetric::Electric,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2328,
|
|
label: "Company-Territory Non-Electric Track Pieces",
|
|
kind: RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
|
|
RuntimeTrackMetric::NonElectric,
|
|
)),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_CANDIDATE_AVAILABILITY_CONDITION_TEMPLATE_ID,
|
|
label: "%1 Avail.",
|
|
kind: RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CandidateAvailability),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_CARGO_PRODUCTION_CONDITION_TEMPLATE_ID,
|
|
label: "%1 Production",
|
|
kind: RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CargoProductionSlot),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_NAMED_LOCOMOTIVE_AVAILABILITY_CONDITION_ID,
|
|
label: "Unknown Loco Available",
|
|
kind: RealOrdinaryConditionKind::WorldState(
|
|
RealWorldConditionKind::NamedLocomotiveAvailability,
|
|
),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_NAMED_LOCOMOTIVE_COST_CONDITION_ID,
|
|
label: "Unknown Loco Cost",
|
|
kind: RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::NamedLocomotiveCost),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_CARGO_PRODUCTION_TOTAL_CONDITION_ID,
|
|
label: "All Cargo Production",
|
|
kind: RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CargoProductionTotal),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_FACTORY_PRODUCTION_TOTAL_CONDITION_ID,
|
|
label: "All Factory Production",
|
|
kind: RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::FactoryProductionTotal),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_FARM_MINE_PRODUCTION_TOTAL_CONDITION_ID,
|
|
label: "All Farm/Mine Production",
|
|
kind: RealOrdinaryConditionKind::WorldState(
|
|
RealWorldConditionKind::FarmMineProductionTotal,
|
|
),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_OTHER_CARGO_PRODUCTION_TOTAL_CONDITION_ID,
|
|
label: "Unknown Cargo Production",
|
|
kind: RealOrdinaryConditionKind::WorldState(
|
|
RealWorldConditionKind::OtherCargoProductionTotal,
|
|
),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_LIMITED_TRACK_BUILDING_AMOUNT_CONDITION_ID,
|
|
label: "Limited Track Building Amount",
|
|
kind: RealOrdinaryConditionKind::WorldState(
|
|
RealWorldConditionKind::LimitedTrackBuildingAmount,
|
|
),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: REAL_TERRITORY_ACCESS_COST_CONDITION_ID,
|
|
label: "Access Rights Cost:",
|
|
kind: RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::TerritoryAccessCost),
|
|
},
|
|
RealOrdinaryConditionMetadata {
|
|
raw_condition_id: 2350,
|
|
label: "Economic Status",
|
|
kind: RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::EconomicStatus),
|
|
},
|
|
];
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
struct KnownSpecialConditionDefinition {
|
|
slot_index: u8,
|
|
hidden: bool,
|
|
label_id: u32,
|
|
help_id: u32,
|
|
label: &'static str,
|
|
}
|
|
|
|
const KNOWN_SPECIAL_CONDITION_DEFINITIONS: [KnownSpecialConditionDefinition;
|
|
SPECIAL_CONDITION_COUNT] = [
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 0,
|
|
hidden: false,
|
|
label_id: 2535,
|
|
help_id: 2564,
|
|
label: "Disable Stock Buying and Selling",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 1,
|
|
hidden: false,
|
|
label_id: 2536,
|
|
help_id: 2565,
|
|
label: "Disable Margin Buying/Short Selling Stock",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 2,
|
|
hidden: false,
|
|
label_id: 2537,
|
|
help_id: 2566,
|
|
label: "Disable Company Issue/Buy Back Stock",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 3,
|
|
hidden: false,
|
|
label_id: 2538,
|
|
help_id: 2567,
|
|
label: "Disable Issuing/Repaying Bonds",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 4,
|
|
hidden: false,
|
|
label_id: 2539,
|
|
help_id: 2568,
|
|
label: "Disable Declaring Bankruptcy",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 5,
|
|
hidden: false,
|
|
label_id: 2540,
|
|
help_id: 2569,
|
|
label: "Disable Changing the Dividend Rate",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 6,
|
|
hidden: false,
|
|
label_id: 2541,
|
|
help_id: 2570,
|
|
label: "Disable Replacing a Locomotive",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 7,
|
|
hidden: false,
|
|
label_id: 2542,
|
|
help_id: 2571,
|
|
label: "Disable Retiring a Train",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 8,
|
|
hidden: false,
|
|
label_id: 2543,
|
|
help_id: 2572,
|
|
label: "Disable Changing Cargo Consist On Train",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 9,
|
|
hidden: false,
|
|
label_id: 2544,
|
|
help_id: 2573,
|
|
label: "Disable Buying a Train",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 10,
|
|
hidden: false,
|
|
label_id: 2545,
|
|
help_id: 2574,
|
|
label: "Disable All Track Building",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 11,
|
|
hidden: false,
|
|
label_id: 2546,
|
|
help_id: 2575,
|
|
label: "Disable Unconnected Track Building",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 12,
|
|
hidden: false,
|
|
label_id: 2547,
|
|
help_id: 2576,
|
|
label: "Limited Track Building Amount",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 13,
|
|
hidden: false,
|
|
label_id: 2548,
|
|
help_id: 2577,
|
|
label: "Disable Building Stations",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 14,
|
|
hidden: false,
|
|
label_id: 2549,
|
|
help_id: 2578,
|
|
label: "Disable Building Hotel/Restaurant/Tavern/Post Office",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 15,
|
|
hidden: false,
|
|
label_id: 2550,
|
|
help_id: 2579,
|
|
label: "Disable Building Customs House",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 16,
|
|
hidden: false,
|
|
label_id: 2551,
|
|
help_id: 2580,
|
|
label: "Disable Building Industry Buildings",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 17,
|
|
hidden: false,
|
|
label_id: 2552,
|
|
help_id: 2581,
|
|
label: "Disable Buying Existing Industry Buildings",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 18,
|
|
hidden: false,
|
|
label_id: 2553,
|
|
help_id: 2582,
|
|
label: "Disable Being Fired As Chairman",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 19,
|
|
hidden: false,
|
|
label_id: 2554,
|
|
help_id: 2583,
|
|
label: "Disable Resigning as Chairman",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 20,
|
|
hidden: false,
|
|
label_id: 2555,
|
|
help_id: 2584,
|
|
label: "Disable Chairmanship Takeover",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 21,
|
|
hidden: false,
|
|
label_id: 2556,
|
|
help_id: 2585,
|
|
label: "Disable Starting Any Companies",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 22,
|
|
hidden: false,
|
|
label_id: 2557,
|
|
help_id: 2586,
|
|
label: "Disable Starting Multiple Companies",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 23,
|
|
hidden: false,
|
|
label_id: 2558,
|
|
help_id: 2587,
|
|
label: "Disable Merging Companies",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 24,
|
|
hidden: false,
|
|
label_id: 2559,
|
|
help_id: 2588,
|
|
label: "Disable Bulldozing",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 25,
|
|
hidden: false,
|
|
label_id: 2560,
|
|
help_id: 2589,
|
|
label: "Show Visited Track",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 26,
|
|
hidden: false,
|
|
label_id: 2561,
|
|
help_id: 2590,
|
|
label: "Show Visited Stations",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 27,
|
|
hidden: false,
|
|
label_id: 2562,
|
|
help_id: 2591,
|
|
label: "Use Slow Date",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 28,
|
|
hidden: false,
|
|
label_id: 2563,
|
|
help_id: 2592,
|
|
label: "Completely Disable Money-Related Things",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 29,
|
|
hidden: false,
|
|
label_id: 2874,
|
|
help_id: 2875,
|
|
label: "Use Bio-Accelerator Cars",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 30,
|
|
hidden: false,
|
|
label_id: 3722,
|
|
help_id: 3723,
|
|
label: "Disable Cargo Economy",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 31,
|
|
hidden: false,
|
|
label_id: 3835,
|
|
help_id: 3836,
|
|
label: "Use Wartime Cargos",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 32,
|
|
hidden: false,
|
|
label_id: 3850,
|
|
help_id: 3851,
|
|
label: "Disable Train Crashes",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 33,
|
|
hidden: false,
|
|
label_id: 3852,
|
|
help_id: 3853,
|
|
label: "Disable Train Crashes AND Breakdowns",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 34,
|
|
hidden: false,
|
|
label_id: 3920,
|
|
help_id: 3921,
|
|
label: "AI Ignore Territories At Startup",
|
|
},
|
|
KnownSpecialConditionDefinition {
|
|
slot_index: 35,
|
|
hidden: true,
|
|
label_id: 3,
|
|
help_id: 3,
|
|
label: "Hidden sentinel",
|
|
},
|
|
];
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
struct KnownTagDefinition {
|
|
tag_id: u16,
|
|
label: &'static str,
|
|
grounded_meaning: &'static str,
|
|
}
|
|
|
|
const KNOWN_TAG_DEFINITIONS: [KnownTagDefinition; 4] = [
|
|
KnownTagDefinition {
|
|
tag_id: 0x2cee,
|
|
label: "overlay_mask_plane_primary",
|
|
grounded_meaning: "Primary one-byte overlay mask plane restored into world offset +0x1655.",
|
|
},
|
|
KnownTagDefinition {
|
|
tag_id: 0x2d51,
|
|
label: "overlay_mask_plane_secondary",
|
|
grounded_meaning: "Secondary one-byte overlay mask plane restored into world offset +0x1659.",
|
|
},
|
|
KnownTagDefinition {
|
|
tag_id: 0x9471,
|
|
label: "sidecar_byte_plane_family_low",
|
|
grounded_meaning: "Lower bound of the grounded sidecar byte-plane chunk family.",
|
|
},
|
|
KnownTagDefinition {
|
|
tag_id: 0x9472,
|
|
label: "sidecar_byte_plane_family_high",
|
|
grounded_meaning: "Upper bound of the grounded sidecar byte-plane chunk family.",
|
|
},
|
|
];
|
|
|
|
fn known_special_condition_definition_for_label_id(
|
|
label_id: u32,
|
|
) -> Option<KnownSpecialConditionDefinition> {
|
|
KNOWN_SPECIAL_CONDITION_DEFINITIONS
|
|
.iter()
|
|
.copied()
|
|
.find(|definition| !definition.hidden && definition.label_id == label_id)
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpKnownTagHit {
|
|
pub tag_id: u16,
|
|
pub tag_hex: String,
|
|
pub label: String,
|
|
pub grounded_meaning: String,
|
|
pub hit_count: usize,
|
|
pub sample_offsets: Vec<usize>,
|
|
pub last_offset: Option<usize>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpPreambleWord {
|
|
pub index: usize,
|
|
pub offset: usize,
|
|
pub value_le: u32,
|
|
pub value_hex: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpPreamble {
|
|
pub byte_len: usize,
|
|
pub word_count: usize,
|
|
pub words: Vec<SmpPreambleWord>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpAsciiPreview {
|
|
pub offset: usize,
|
|
pub byte_len: usize,
|
|
pub preview: String,
|
|
pub truncated: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSharedHeader {
|
|
pub byte_len: usize,
|
|
pub root_kind_word: u32,
|
|
pub root_kind_word_hex: String,
|
|
pub primary_family_tag: u32,
|
|
pub primary_family_tag_hex: String,
|
|
pub shared_signature_words_1_to_7: Vec<u32>,
|
|
pub shared_signature_hex_words_1_to_7: Vec<String>,
|
|
pub matches_grounded_common_signature: bool,
|
|
pub payload_window_words_8_to_9: Vec<u32>,
|
|
pub payload_window_hex_words_8_to_9: Vec<String>,
|
|
pub reserved_words_10_to_14: Vec<u32>,
|
|
pub reserved_words_10_to_14_all_zero: bool,
|
|
pub final_flag_word: u32,
|
|
pub final_flag_word_hex: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpHeaderVariantProbe {
|
|
pub variant_family: String,
|
|
pub variant_evidence: Vec<String>,
|
|
pub is_known_family: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpEarlyContentProbe {
|
|
pub first_post_text_nonzero_offset: usize,
|
|
pub zero_pad_after_text_len: usize,
|
|
pub first_post_text_block_len: usize,
|
|
pub first_post_text_block_hex: String,
|
|
pub trailing_zero_pad_after_first_block_len: usize,
|
|
pub secondary_nonzero_offset: Option<usize>,
|
|
pub secondary_aligned_word_window_offset: Option<usize>,
|
|
pub secondary_aligned_word_window_words: Vec<u32>,
|
|
pub secondary_aligned_word_window_hex_words: Vec<String>,
|
|
pub secondary_preview_hex: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSecondaryVariantProbe {
|
|
pub aligned_window_offset: usize,
|
|
pub words: Vec<u32>,
|
|
pub hex_words: Vec<String>,
|
|
pub variant_family: String,
|
|
pub variant_evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpContainerProfile {
|
|
pub profile_family: String,
|
|
pub profile_evidence: Vec<String>,
|
|
pub is_known_profile: bool,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveBootstrapBlock {
|
|
pub profile_family: String,
|
|
pub aligned_window_offset: usize,
|
|
pub leading_word: u32,
|
|
pub leading_word_hex: String,
|
|
pub anchor_word: u32,
|
|
pub anchor_word_hex: String,
|
|
pub descriptor_word_2: u32,
|
|
pub descriptor_word_2_hex: String,
|
|
pub descriptor_word_3: u32,
|
|
pub descriptor_word_3_hex: String,
|
|
pub descriptor_word_4: u32,
|
|
pub descriptor_word_4_hex: String,
|
|
pub descriptor_word_5: u32,
|
|
pub descriptor_word_5_hex: String,
|
|
pub descriptor_word_6: u32,
|
|
pub descriptor_word_6_hex: String,
|
|
pub descriptor_word_7: u32,
|
|
pub descriptor_word_7_hex: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveAnchorRunBlock {
|
|
pub profile_family: String,
|
|
pub cycle_start_offset: usize,
|
|
pub cycle_words: Vec<u32>,
|
|
pub cycle_hex_words: Vec<String>,
|
|
pub full_cycle_count: usize,
|
|
pub partial_cycle_word_count: usize,
|
|
pub trailer_offset: usize,
|
|
pub trailer_words: Vec<u32>,
|
|
pub trailer_hex_words: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpRuntimeAnchorCycleBlock {
|
|
pub profile_family: String,
|
|
pub cycle_start_offset: usize,
|
|
pub cycle_words: Vec<u32>,
|
|
pub cycle_hex_words: Vec<String>,
|
|
pub full_cycle_count: usize,
|
|
pub partial_cycle_word_count: usize,
|
|
pub trailer_offset: usize,
|
|
pub trailer_words: Vec<u32>,
|
|
pub trailer_hex_words: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpRuntimeTrailerBlock {
|
|
pub profile_family: String,
|
|
pub trailer_family: String,
|
|
pub trailer_evidence: Vec<String>,
|
|
pub trailer_offset: usize,
|
|
pub prefix_words_0_to_5: Vec<u32>,
|
|
pub prefix_hex_words_0_to_5: Vec<String>,
|
|
pub tag_word_6: u32,
|
|
pub tag_word_6_hex: String,
|
|
pub tag_chunk_id_u16: u16,
|
|
pub tag_chunk_id_hex: String,
|
|
pub tag_chunk_id_grounded_alignment: Option<String>,
|
|
pub length_word_7: u32,
|
|
pub length_word_7_hex: String,
|
|
pub length_high_u16: u16,
|
|
pub length_high_hex: String,
|
|
pub selector_word_8: u32,
|
|
pub selector_word_8_hex: String,
|
|
pub selector_high_u16: u16,
|
|
pub selector_high_hex: String,
|
|
pub layout_word_9: u32,
|
|
pub layout_word_9_hex: String,
|
|
pub descriptor_word_10: u32,
|
|
pub descriptor_word_10_hex: String,
|
|
pub descriptor_high_u16: u16,
|
|
pub descriptor_high_hex: String,
|
|
pub descriptor_word_11: u32,
|
|
pub descriptor_word_11_hex: String,
|
|
pub counter_word_12: u32,
|
|
pub counter_word_12_hex: String,
|
|
pub offset_word_13: u32,
|
|
pub offset_word_13_hex: String,
|
|
pub span_word_14: u32,
|
|
pub span_word_14_hex: String,
|
|
pub mode_word_15: u32,
|
|
pub mode_word_15_hex: String,
|
|
pub words: Vec<u32>,
|
|
pub hex_words: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpRuntimePostSpanProbe {
|
|
pub profile_family: String,
|
|
pub span_target_offset: usize,
|
|
pub next_nonzero_offset: Option<usize>,
|
|
pub next_aligned_candidate_offset: Option<usize>,
|
|
pub next_aligned_candidate_words: Vec<u32>,
|
|
pub next_aligned_candidate_hex_words: Vec<String>,
|
|
pub header_candidates: Vec<SmpRuntimePostSpanHeaderCandidate>,
|
|
pub grounded_progress_hits: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpRuntimePostSpanHeaderCandidate {
|
|
pub offset: usize,
|
|
pub words: Vec<u32>,
|
|
pub hex_words: Vec<String>,
|
|
pub dense_word_count: usize,
|
|
pub high_u16_words: Vec<u16>,
|
|
pub high_hex_words: Vec<String>,
|
|
pub grounded_alignments: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpRt3105PostSpanBridgeProbe {
|
|
pub profile_family: String,
|
|
pub bridge_family: String,
|
|
pub bridge_evidence: Vec<String>,
|
|
pub span_target_offset: usize,
|
|
pub next_candidate_offset: Option<usize>,
|
|
pub next_candidate_delta_from_span_target: Option<usize>,
|
|
pub packed_profile_offset: usize,
|
|
pub packed_profile_delta_from_span_target: usize,
|
|
pub next_candidate_delta_from_packed_profile: Option<i64>,
|
|
pub selector_high_u16: u16,
|
|
pub selector_high_hex: String,
|
|
pub descriptor_high_u16: u16,
|
|
pub descriptor_high_hex: String,
|
|
pub next_candidate_high_u16_words: Vec<u16>,
|
|
pub next_candidate_high_hex_words: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpRt3105SaveBridgePayloadProbe {
|
|
pub profile_family: String,
|
|
pub bridge_family: String,
|
|
pub primary_block_offset: usize,
|
|
pub primary_block_len: usize,
|
|
pub primary_block_len_hex: String,
|
|
pub primary_words: Vec<u32>,
|
|
pub primary_hex_words: Vec<String>,
|
|
pub secondary_block_offset: usize,
|
|
pub secondary_block_delta_from_primary: usize,
|
|
pub secondary_block_delta_from_primary_hex: String,
|
|
pub secondary_block_end_offset: usize,
|
|
pub secondary_block_len: usize,
|
|
pub secondary_block_len_hex: String,
|
|
pub secondary_preview_word_count: usize,
|
|
pub secondary_words: Vec<u32>,
|
|
pub secondary_hex_words: Vec<String>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveWorldSelectionContextProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub chunk_tag_offset: usize,
|
|
pub payload_offset: usize,
|
|
pub payload_len: usize,
|
|
pub payload_len_hex: String,
|
|
pub selected_company_id_offset: usize,
|
|
pub selected_company_id: u32,
|
|
pub selected_company_id_hex: String,
|
|
pub selected_chairman_profile_id_offset: usize,
|
|
pub selected_chairman_profile_id: u32,
|
|
pub selected_chairman_profile_id_hex: String,
|
|
pub chairman_slot_selector_offset: usize,
|
|
pub chairman_slot_selectors: Vec<u8>,
|
|
pub campaign_override_flag_offset: usize,
|
|
pub campaign_override_flag: u8,
|
|
pub campaign_override_flag_hex: String,
|
|
pub chairman_role_gate_offset: usize,
|
|
pub chairman_role_gate_bytes: Vec<u8>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpSaveWorldEconomicTuningProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub chunk_tag_offset: usize,
|
|
pub payload_offset: usize,
|
|
pub payload_len: usize,
|
|
pub payload_len_hex: String,
|
|
pub mirror_lane: SmpSaveDwordCandidate,
|
|
pub tuning_lanes: Vec<SmpSaveDwordCandidate>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpSaveWorldIssue37Probe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub chunk_tag_offset: usize,
|
|
pub payload_offset: usize,
|
|
pub payload_len: usize,
|
|
pub payload_len_hex: String,
|
|
pub issue_37_raw_u8: u8,
|
|
pub issue_37_raw_hex: String,
|
|
pub issue_38_raw_u8: u8,
|
|
pub issue_38_raw_hex: String,
|
|
pub issue_39_raw_u8: u8,
|
|
pub issue_39_raw_hex: String,
|
|
pub issue_3a_raw_u8: u8,
|
|
pub issue_3a_raw_hex: String,
|
|
pub issue_value_lane: SmpSaveDwordCandidate,
|
|
pub multiplier_lane: SmpSaveDwordCandidate,
|
|
#[serde(default)]
|
|
pub issue_opinion_base_terms_raw_i32: Vec<i32>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpSaveWorldFinanceNeighborhoodProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub chunk_tag_offset: usize,
|
|
pub payload_offset: usize,
|
|
pub payload_len: usize,
|
|
pub payload_len_hex: String,
|
|
pub packed_year_word_raw_u16: u16,
|
|
pub packed_year_word_raw_hex: String,
|
|
pub partial_year_progress_raw_u8: u8,
|
|
pub partial_year_progress_raw_hex: String,
|
|
pub current_calendar_tuple_word_lane: SmpSaveDwordCandidate,
|
|
pub current_calendar_tuple_word_2_lane: SmpSaveDwordCandidate,
|
|
pub absolute_counter_lane: SmpSaveDwordCandidate,
|
|
pub absolute_counter_mirror_lane: SmpSaveDwordCandidate,
|
|
pub stock_policy_raw_u8: u8,
|
|
pub stock_policy_raw_hex: String,
|
|
pub bond_policy_raw_u8: u8,
|
|
pub bond_policy_raw_hex: String,
|
|
pub bankruptcy_policy_raw_u8: u8,
|
|
pub bankruptcy_policy_raw_hex: String,
|
|
pub dividend_policy_raw_u8: u8,
|
|
pub dividend_policy_raw_hex: String,
|
|
pub building_density_growth_setting_lane: SmpSaveDwordCandidate,
|
|
pub dword_candidates: Vec<SmpSaveDwordCandidate>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveTaggedCollectionHeaderProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub metadata_tag_offset: usize,
|
|
pub records_tag_offset: usize,
|
|
pub close_tag_offset: usize,
|
|
pub direct_collection_flag: u32,
|
|
pub direct_collection_flag_hex: String,
|
|
pub direct_record_stride: u32,
|
|
pub direct_record_stride_hex: String,
|
|
pub live_id_bound: u32,
|
|
pub live_id_bound_hex: String,
|
|
pub live_record_count: u32,
|
|
pub live_record_count_hex: String,
|
|
pub header_words: Vec<u32>,
|
|
pub header_hex_words: Vec<String>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveUnclassifiedTaggedCollectionHeaderProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub metadata_tag: u32,
|
|
pub metadata_tag_hex: String,
|
|
pub records_tag: u32,
|
|
pub records_tag_hex: String,
|
|
pub close_tag: u32,
|
|
pub close_tag_hex: String,
|
|
pub metadata_tag_offset: usize,
|
|
pub records_tag_offset: usize,
|
|
pub close_tag_offset: usize,
|
|
pub records_span_len: usize,
|
|
pub direct_collection_flag: u32,
|
|
pub direct_collection_flag_hex: String,
|
|
pub direct_record_stride: u32,
|
|
pub direct_record_stride_hex: String,
|
|
pub live_id_bound: u32,
|
|
pub live_id_bound_hex: String,
|
|
pub live_record_count: u32,
|
|
pub live_record_count_hex: String,
|
|
pub header_words: Vec<u32>,
|
|
pub header_hex_words: Vec<String>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveTrainCollectionDirectoryEntryProbe {
|
|
pub live_entry_id: u32,
|
|
pub payload_relative_offset: u32,
|
|
pub payload_relative_offset_hex: String,
|
|
pub payload_absolute_offset: usize,
|
|
pub previous_live_entry_id: u32,
|
|
pub previous_live_entry_id_hex: String,
|
|
pub next_live_entry_id: u32,
|
|
pub next_live_entry_id_hex: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveTrainCollectionDirectoryProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub metadata_tag_offset: usize,
|
|
pub records_tag_offset: usize,
|
|
pub close_tag_offset: usize,
|
|
pub directory_root_dword_index: usize,
|
|
pub directory_entry_dword_count: usize,
|
|
pub live_record_count: u32,
|
|
pub live_id_bound: u32,
|
|
#[serde(default)]
|
|
pub chain_head_live_entry_id: Option<u32>,
|
|
#[serde(default)]
|
|
pub chain_tail_live_entry_id: Option<u32>,
|
|
pub entries: Vec<SmpSaveTrainCollectionDirectoryEntryProbe>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpSaveRegionProfileEntryProbe {
|
|
pub entry_index: usize,
|
|
pub row_relative_offset: usize,
|
|
pub name: String,
|
|
pub trailing_weight_f32: f32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpSaveRegionProfileCollectionProbe {
|
|
pub direct_collection_flag: u32,
|
|
pub entry_stride: u32,
|
|
pub live_id_bound: u32,
|
|
pub live_record_count: u32,
|
|
pub entry_start_relative_offset: usize,
|
|
pub trailing_padding_len: usize,
|
|
#[serde(default)]
|
|
pub entries: Vec<SmpSaveRegionProfileEntryProbe>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpSaveRegionRecordTripletEntryProbe {
|
|
pub record_index: usize,
|
|
pub name: String,
|
|
pub record_payload_relative_offset: usize,
|
|
pub record_payload_relative_offset_hex: String,
|
|
pub name_tag_relative_offset: usize,
|
|
pub policy_tag_relative_offset: usize,
|
|
pub profile_tag_relative_offset: usize,
|
|
pub pre_name_prefix_len: usize,
|
|
#[serde(default)]
|
|
pub pre_name_prefix_hex_bytes: Vec<String>,
|
|
#[serde(default)]
|
|
pub pre_name_prefix_dword_candidates: Vec<SmpSaveDwordCandidate>,
|
|
pub policy_chunk_len: usize,
|
|
pub profile_chunk_len: usize,
|
|
pub policy_leading_f32_0: f32,
|
|
pub policy_leading_f32_1: f32,
|
|
pub policy_leading_f32_2: f32,
|
|
#[serde(default)]
|
|
pub policy_reserved_dwords: Vec<u32>,
|
|
#[serde(default)]
|
|
pub policy_reserved_dword_candidates: Vec<SmpSaveDwordCandidate>,
|
|
pub policy_trailing_word: u16,
|
|
pub policy_trailing_word_hex: String,
|
|
#[serde(default)]
|
|
pub profile_collection: Option<SmpSaveRegionProfileCollectionProbe>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpSaveRegionRecordTripletProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub records_tag_offset: usize,
|
|
pub close_tag_offset: usize,
|
|
pub record_count: usize,
|
|
#[serde(default)]
|
|
pub entries: Vec<SmpSaveRegionRecordTripletEntryProbe>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedRegionProfileEntry {
|
|
pub entry_index: usize,
|
|
pub name: String,
|
|
pub trailing_weight_f32: f32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedRegionProfileCollection {
|
|
pub direct_collection_flag: u32,
|
|
pub entry_stride: u32,
|
|
pub live_id_bound: u32,
|
|
pub live_record_count: u32,
|
|
pub trailing_padding_len: usize,
|
|
#[serde(default)]
|
|
pub entries: Vec<SmpLoadedRegionProfileEntry>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedRegionEntry {
|
|
pub record_index: usize,
|
|
pub name: String,
|
|
pub pre_name_prefix_len: usize,
|
|
pub policy_leading_f32_0: f32,
|
|
pub policy_leading_f32_1: f32,
|
|
pub policy_leading_f32_2: f32,
|
|
#[serde(default)]
|
|
pub policy_reserved_dwords: Vec<u32>,
|
|
pub policy_trailing_word: u16,
|
|
pub policy_trailing_word_hex: String,
|
|
#[serde(default)]
|
|
pub profile_collection: Option<SmpLoadedRegionProfileCollection>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedRegionCollection {
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub observed_entry_count: usize,
|
|
#[serde(default)]
|
|
pub entries: Vec<SmpLoadedRegionEntry>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedRegionFixedRowRunSummary {
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub target_row_count: usize,
|
|
pub target_row_stride: usize,
|
|
pub target_row_stride_hex: String,
|
|
#[serde(default)]
|
|
pub candidates: Vec<SmpSaveRegionFixedRowRunCandidate>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveRegionQueuedNoticeRecordEntryProbe {
|
|
pub node_base_offset: usize,
|
|
pub payload_seed_offset: usize,
|
|
pub next_link_raw: u32,
|
|
pub next_link_raw_hex: String,
|
|
pub payload_seed_dword: u32,
|
|
pub payload_seed_dword_hex: String,
|
|
pub kind: u32,
|
|
pub kind_hex: String,
|
|
pub promotion_latch_dword: u32,
|
|
pub promotion_latch_dword_hex: String,
|
|
pub region_id: u32,
|
|
pub region_id_hex: String,
|
|
pub amount: u32,
|
|
pub amount_hex: String,
|
|
pub trailing_sentinel_i32_0: i32,
|
|
pub trailing_sentinel_i32_0_hex: String,
|
|
pub trailing_sentinel_i32_1: i32,
|
|
pub trailing_sentinel_i32_1_hex: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveRegionQueuedNoticeRecordProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub payload_seed_dword: u32,
|
|
pub payload_seed_dword_hex: String,
|
|
#[serde(default)]
|
|
pub entries: Vec<SmpSaveRegionQueuedNoticeRecordEntryProbe>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveFixedRowRunDwordLaneSummary {
|
|
pub relative_offset: usize,
|
|
pub relative_offset_hex: String,
|
|
pub zero_count: usize,
|
|
pub nonzero_count: usize,
|
|
pub distinct_value_count: usize,
|
|
pub probable_normal_f32_count: usize,
|
|
pub small_unsigned_count: usize,
|
|
#[serde(default)]
|
|
pub sample_values_hex: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveRegionFixedRowRunCandidate {
|
|
pub count_offset: usize,
|
|
pub count_offset_hex: String,
|
|
pub row_count: usize,
|
|
pub row_stride: usize,
|
|
pub row_stride_hex: String,
|
|
pub rows_offset: usize,
|
|
pub rows_offset_hex: String,
|
|
pub rows_end_offset: usize,
|
|
pub rows_end_offset_hex: String,
|
|
pub distance_to_region_metadata_tag: usize,
|
|
pub distance_to_region_metadata_tag_hex: String,
|
|
#[serde(default)]
|
|
pub dword_lane_summaries: Vec<SmpSaveFixedRowRunDwordLaneSummary>,
|
|
pub shape_signature: String,
|
|
pub shape_family_signature: String,
|
|
pub trailing_byte_zero_count: usize,
|
|
pub trailing_byte_nonzero_count: usize,
|
|
pub trailing_byte_distinct_value_count: usize,
|
|
#[serde(default)]
|
|
pub trailing_byte_sample_values_hex: Vec<String>,
|
|
#[serde(default)]
|
|
pub best_probable_density_lane_relative_offset_hex: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveRegionFixedRowRunCandidateProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub target_row_count: usize,
|
|
pub target_row_stride: usize,
|
|
pub target_row_stride_hex: String,
|
|
pub scan_start_offset: usize,
|
|
pub scan_start_offset_hex: String,
|
|
pub scan_end_offset: usize,
|
|
pub scan_end_offset_hex: String,
|
|
#[serde(default)]
|
|
pub candidates: Vec<SmpSaveRegionFixedRowRunCandidate>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveRegionFixedRowRunSharedShapeMatch {
|
|
pub shape_signature: String,
|
|
pub left_rank: usize,
|
|
pub left_rows_offset_hex: String,
|
|
pub left_best_probable_density_lane_relative_offset_hex: Option<String>,
|
|
pub right_rank: usize,
|
|
pub right_rows_offset_hex: String,
|
|
pub right_best_probable_density_lane_relative_offset_hex: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveRegionFixedRowRunComparisonReport {
|
|
pub left_profile_family: String,
|
|
pub right_profile_family: String,
|
|
pub left_best_rows_offset_hex: Option<String>,
|
|
pub right_best_rows_offset_hex: Option<String>,
|
|
pub left_best_shape_signature: Option<String>,
|
|
pub right_best_shape_signature: Option<String>,
|
|
pub left_best_shape_family_signature: Option<String>,
|
|
pub right_best_shape_family_signature: Option<String>,
|
|
#[serde(default)]
|
|
pub shared_shape_matches: Vec<SmpSaveRegionFixedRowRunSharedShapeMatch>,
|
|
#[serde(default)]
|
|
pub shared_shape_family_matches: Vec<SmpSaveRegionFixedRowRunSharedShapeMatch>,
|
|
#[serde(default)]
|
|
pub left_only_shape_signatures: Vec<String>,
|
|
#[serde(default)]
|
|
pub right_only_shape_signatures: Vec<String>,
|
|
#[serde(default)]
|
|
pub left_only_shape_family_signatures: Vec<String>,
|
|
#[serde(default)]
|
|
pub right_only_shape_family_signatures: Vec<String>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureRecordTripletEntryProbe {
|
|
pub record_index: usize,
|
|
pub primary_name: String,
|
|
pub secondary_name: String,
|
|
pub name_tag_relative_offset: usize,
|
|
pub policy_tag_relative_offset: usize,
|
|
pub profile_tag_relative_offset: usize,
|
|
pub policy_chunk_len: usize,
|
|
pub profile_chunk_len: usize,
|
|
pub policy_f32_lane_0: f32,
|
|
pub policy_f32_lane_1: f32,
|
|
pub policy_f32_lane_2: f32,
|
|
pub policy_f32_lane_3: f32,
|
|
pub policy_f32_lane_4: f32,
|
|
pub policy_reserved_dword: u32,
|
|
pub policy_trailing_word: u16,
|
|
pub policy_trailing_word_hex: String,
|
|
pub profile_open_marker: u32,
|
|
pub profile_open_marker_hex: String,
|
|
pub profile_repeated_primary_name: String,
|
|
pub profile_repeated_secondary_name: String,
|
|
pub profile_footer_relative_offset: usize,
|
|
pub profile_footer_relative_offset_hex: String,
|
|
pub profile_pre_footer_padding_len: usize,
|
|
#[serde(default)]
|
|
pub profile_pre_footer_padding_hex_bytes: Vec<String>,
|
|
#[serde(default)]
|
|
pub profile_companion_byte_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub profile_companion_byte_hex: Option<String>,
|
|
pub profile_payload_dword: u32,
|
|
pub profile_payload_dword_hex: String,
|
|
pub profile_sentinel_i32: i32,
|
|
pub profile_status_kind: String,
|
|
pub farm_growth_stage_index: Option<u8>,
|
|
pub profile_close_marker: u32,
|
|
pub profile_close_marker_hex: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureRecordTripletProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub records_tag_offset: usize,
|
|
pub close_tag_offset: usize,
|
|
pub record_count: usize,
|
|
#[serde(default)]
|
|
pub entries: Vec<SmpSavePlacedStructureRecordTripletEntryProbe>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedPlacedStructureEntry {
|
|
pub record_index: usize,
|
|
pub primary_name: String,
|
|
pub secondary_name: String,
|
|
pub policy_trailing_word: u16,
|
|
pub policy_trailing_word_hex: String,
|
|
pub profile_payload_dword: u32,
|
|
pub profile_payload_dword_hex: String,
|
|
pub profile_status_kind: String,
|
|
#[serde(default)]
|
|
pub farm_growth_stage_index: Option<u8>,
|
|
#[serde(default)]
|
|
pub profile_companion_byte_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub profile_companion_byte_hex: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedPlacedStructureCollection {
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub observed_entry_count: usize,
|
|
#[serde(default)]
|
|
pub entries: Vec<SmpLoadedPlacedStructureEntry>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub metadata_tag_offset: usize,
|
|
pub records_tag_offset: usize,
|
|
pub close_tag_offset: usize,
|
|
pub records_span_len: usize,
|
|
pub direct_record_stride: u32,
|
|
pub direct_record_stride_hex: String,
|
|
pub live_id_bound: u32,
|
|
pub live_id_bound_hex: String,
|
|
pub live_record_count: u32,
|
|
pub live_record_count_hex: String,
|
|
pub owner_shared_dword: u32,
|
|
pub owner_shared_dword_hex: String,
|
|
pub owner_shared_dword_relative_offset: usize,
|
|
pub owner_shared_dword_matches_first_compact_prefix_leading_dword: bool,
|
|
#[serde(default)]
|
|
pub first_record_child_count_after_owner_shared: Option<u16>,
|
|
#[serde(default)]
|
|
pub first_record_child_count_after_owner_shared_hex: Option<String>,
|
|
#[serde(default)]
|
|
pub first_record_saved_primary_child_byte_after_owner_shared: Option<u8>,
|
|
#[serde(default)]
|
|
pub first_record_saved_primary_child_byte_after_owner_shared_hex: Option<String>,
|
|
#[serde(default)]
|
|
pub first_record_first_name_tag_relative_offset_after_owner_shared: Option<usize>,
|
|
pub prefix_leading_dword: u32,
|
|
pub prefix_leading_dword_hex: String,
|
|
pub prefix_trailing_word: u16,
|
|
pub prefix_trailing_word_hex: String,
|
|
pub prefix_separator_byte: u8,
|
|
pub prefix_separator_byte_hex: String,
|
|
pub first_embedded_name_tag_relative_offset: usize,
|
|
pub embedded_name_tag_count: usize,
|
|
pub decoded_embedded_name_row_count: usize,
|
|
pub decoded_embedded_name_row_with_tertiary_name_count: usize,
|
|
pub unique_compact_prefix_pattern_count: usize,
|
|
pub prefix_leading_dword_matching_embedded_profile_tag_count: usize,
|
|
pub unique_embedded_name_pair_count: usize,
|
|
#[serde(default)]
|
|
pub first_embedded_primary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub first_embedded_secondary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub first_embedded_tertiary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub embedded_name_row_samples: Vec<SmpSavePlacedStructureDynamicSideBufferSampleEntry>,
|
|
#[serde(default)]
|
|
pub compact_prefix_pattern_summaries:
|
|
Vec<SmpSavePlacedStructureDynamicSideBufferPrefixPatternSummary>,
|
|
#[serde(default)]
|
|
pub name_pair_summaries: Vec<SmpSavePlacedStructureDynamicSideBufferNamePairSummary>,
|
|
#[serde(default)]
|
|
pub payload_envelope_summary:
|
|
Option<SmpSavePlacedStructureDynamicSideBufferPayloadEnvelopeSummary>,
|
|
#[serde(default)]
|
|
pub live_entry_prelude_summary:
|
|
Option<SmpSavePlacedStructureDynamicSideBufferLiveEntryPreludeSummary>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedPlacedStructureDynamicSideBufferSummary {
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub observed_entry_count: u32,
|
|
pub owner_shared_dword_hex: String,
|
|
pub unique_embedded_name_pair_count: usize,
|
|
pub decoded_embedded_name_row_count: usize,
|
|
pub first_prefix_leading_dword_hex: String,
|
|
pub first_prefix_trailing_word_hex: String,
|
|
pub first_prefix_separator_byte_hex: String,
|
|
pub triplet_alignment_overlap_count: usize,
|
|
pub triplet_alignment_side_buffer_only_name_pair_count: usize,
|
|
#[serde(default)]
|
|
pub compact_prefix_pattern_summaries:
|
|
Vec<SmpSavePlacedStructureDynamicSideBufferPrefixPatternSummary>,
|
|
#[serde(default)]
|
|
pub name_pair_summaries: Vec<SmpSavePlacedStructureDynamicSideBufferNamePairSummary>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferSampleEntry {
|
|
pub sample_index: usize,
|
|
pub name_tag_relative_offset: usize,
|
|
pub prefix_leading_dword: u32,
|
|
pub prefix_leading_dword_hex: String,
|
|
pub prefix_trailing_word: u16,
|
|
pub prefix_trailing_word_hex: String,
|
|
pub prefix_separator_byte: u8,
|
|
pub prefix_separator_byte_hex: String,
|
|
#[serde(default)]
|
|
pub primary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub secondary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub tertiary_name: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferPrefixPatternSummary {
|
|
pub prefix_leading_dword: u32,
|
|
pub prefix_leading_dword_hex: String,
|
|
pub prefix_trailing_word: u16,
|
|
pub prefix_trailing_word_hex: String,
|
|
pub prefix_separator_byte: u8,
|
|
pub prefix_separator_byte_hex: String,
|
|
pub count: usize,
|
|
pub first_name_tag_relative_offset: usize,
|
|
pub prefix_leading_dword_matches_embedded_profile_tag: bool,
|
|
pub section_like_primary_name_count: usize,
|
|
pub cap_like_primary_name_count: usize,
|
|
pub other_primary_name_count: usize,
|
|
#[serde(default)]
|
|
pub first_primary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub first_secondary_name: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferNamePairSummary {
|
|
pub primary_name: String,
|
|
pub secondary_name: String,
|
|
pub count: usize,
|
|
pub first_name_tag_relative_offset: usize,
|
|
pub unique_compact_prefix_pattern_count: usize,
|
|
pub dominant_prefix_leading_dword: u32,
|
|
pub dominant_prefix_leading_dword_hex: String,
|
|
pub dominant_prefix_trailing_word: u16,
|
|
pub dominant_prefix_trailing_word_hex: String,
|
|
pub dominant_prefix_separator_byte: u8,
|
|
pub dominant_prefix_separator_byte_hex: String,
|
|
pub dominant_prefix_count: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferPayloadEnvelopeSummary {
|
|
pub row_count_with_policy_tag_before_next_name: usize,
|
|
pub row_count_with_complete_0x55f1_0x55f2_0x55f3_envelope: usize,
|
|
pub row_count_missing_policy_tag_before_next_name: usize,
|
|
pub row_count_missing_profile_tag_after_policy: usize,
|
|
#[serde(default)]
|
|
pub unique_policy_chunk_lens: Vec<usize>,
|
|
#[serde(default)]
|
|
pub unique_profile_chunk_lens: Vec<usize>,
|
|
#[serde(default)]
|
|
pub dominant_policy_chunk_len: Option<usize>,
|
|
pub dominant_policy_chunk_len_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_profile_chunk_len: Option<usize>,
|
|
pub dominant_profile_chunk_len_count: usize,
|
|
#[serde(default)]
|
|
pub short_profile_flag_pair_summary:
|
|
Option<SmpSavePlacedStructureDynamicSideBufferShortProfileFlagPairSummary>,
|
|
#[serde(default)]
|
|
pub fixed_policy_summary: Option<SmpSavePlacedStructureDynamicSideBufferFixedPolicySummary>,
|
|
#[serde(default)]
|
|
pub name_prelude_candidate_summary:
|
|
Option<SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidateSummary>,
|
|
#[serde(default)]
|
|
pub dominant_profile_span_class_summary:
|
|
Option<SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanClassSummary>,
|
|
#[serde(default)]
|
|
pub sample_rows: Vec<SmpSavePlacedStructureDynamicSideBufferPayloadEnvelopeSample>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanClassSummary {
|
|
pub profile_chunk_len_to_next_name_or_end: usize,
|
|
pub row_count: usize,
|
|
pub unique_name_pair_count: usize,
|
|
pub unique_compact_prefix_pattern_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_candidate_pattern:
|
|
Option<SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern>,
|
|
#[serde(default)]
|
|
pub dominant_primary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub dominant_secondary_name: Option<String>,
|
|
pub dominant_name_pair_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_prefix_leading_dword: Option<u32>,
|
|
#[serde(default)]
|
|
pub dominant_prefix_leading_dword_hex: Option<String>,
|
|
#[serde(default)]
|
|
pub dominant_prefix_trailing_word: Option<u16>,
|
|
#[serde(default)]
|
|
pub dominant_prefix_trailing_word_hex: Option<String>,
|
|
#[serde(default)]
|
|
pub dominant_prefix_separator_byte: Option<u8>,
|
|
#[serde(default)]
|
|
pub dominant_prefix_separator_byte_hex: Option<String>,
|
|
pub dominant_prefix_count: usize,
|
|
#[serde(default)]
|
|
pub sample_rows: Vec<SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanSample>,
|
|
#[serde(default)]
|
|
pub name_pair_summaries:
|
|
Vec<SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanNamePairSummary>,
|
|
#[serde(default)]
|
|
pub compact_prefix_pattern_summaries:
|
|
Vec<SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanPrefixSummary>,
|
|
#[serde(default)]
|
|
pub candidate_pattern_summaries:
|
|
Vec<SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanSample {
|
|
pub sample_index: usize,
|
|
pub name_tag_relative_offset: usize,
|
|
#[serde(default)]
|
|
pub primary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub secondary_name: Option<String>,
|
|
pub prefix_leading_dword: u32,
|
|
pub prefix_leading_dword_hex: String,
|
|
pub prefix_trailing_word: u16,
|
|
pub prefix_trailing_word_hex: String,
|
|
pub prefix_separator_byte: u8,
|
|
pub prefix_separator_byte_hex: String,
|
|
#[serde(default)]
|
|
pub child_count_candidate: Option<u16>,
|
|
#[serde(default)]
|
|
pub child_count_candidate_hex: Option<String>,
|
|
#[serde(default)]
|
|
pub saved_primary_child_byte_candidate: Option<u8>,
|
|
#[serde(default)]
|
|
pub saved_primary_child_byte_candidate_hex: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanNamePairSummary {
|
|
#[serde(default)]
|
|
pub primary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub secondary_name: Option<String>,
|
|
pub count: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanPrefixSummary {
|
|
pub prefix_leading_dword: u32,
|
|
pub prefix_leading_dword_hex: String,
|
|
pub prefix_trailing_word: u16,
|
|
pub prefix_trailing_word_hex: String,
|
|
pub prefix_separator_byte: u8,
|
|
pub prefix_separator_byte_hex: String,
|
|
pub count: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferFixedPolicySummary {
|
|
pub row_count_with_0x1a_policy_chunk: usize,
|
|
pub unique_trailing_word_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_trailing_word: Option<u16>,
|
|
#[serde(default)]
|
|
pub dominant_trailing_word_hex: Option<String>,
|
|
pub dominant_trailing_word_count: usize,
|
|
#[serde(default)]
|
|
pub compact_prefix_correlations:
|
|
Vec<SmpSavePlacedStructureDynamicSideBufferFixedPolicyCompactPrefixCorrelation>,
|
|
#[serde(default)]
|
|
pub sample_rows: Vec<SmpSavePlacedStructureDynamicSideBufferFixedPolicySample>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferFixedPolicySample {
|
|
pub sample_index: usize,
|
|
pub name_tag_relative_offset: usize,
|
|
#[serde(default)]
|
|
pub primary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub secondary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub first_triplet_dwords_hex: Vec<String>,
|
|
#[serde(default)]
|
|
pub second_triplet_dwords_hex: Vec<String>,
|
|
pub trailing_word: u16,
|
|
pub trailing_word_hex: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferFixedPolicyCompactPrefixCorrelation {
|
|
pub prefix_leading_dword: u32,
|
|
pub prefix_leading_dword_hex: String,
|
|
pub prefix_trailing_word: u16,
|
|
pub prefix_trailing_word_hex: String,
|
|
pub prefix_separator_byte: u8,
|
|
pub prefix_separator_byte_hex: String,
|
|
pub row_count: usize,
|
|
pub unique_policy_tuple_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_primary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub dominant_secondary_name: Option<String>,
|
|
pub dominant_name_pair_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_mode_family: Option<String>,
|
|
pub dominant_mode_family_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_first_triplet_dwords_hex: Vec<String>,
|
|
#[serde(default)]
|
|
pub dominant_second_triplet_dwords_hex: Vec<String>,
|
|
#[serde(default)]
|
|
pub dominant_trailing_word: Option<u16>,
|
|
#[serde(default)]
|
|
pub dominant_trailing_word_hex: Option<String>,
|
|
pub dominant_policy_tuple_count: usize,
|
|
#[serde(default)]
|
|
pub mode_family_counts: Vec<SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount>,
|
|
#[serde(default)]
|
|
pub sample_rows: Vec<SmpSavePlacedStructureDynamicSideBufferFixedPolicySample>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferShortProfileFlagPairSummary {
|
|
pub row_count_with_0x06_profile_span: usize,
|
|
pub unique_flag_pair_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_first_flag_byte: Option<u8>,
|
|
#[serde(default)]
|
|
pub dominant_first_flag_byte_hex: Option<String>,
|
|
pub dominant_first_flag_byte_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_second_flag_byte: Option<u8>,
|
|
#[serde(default)]
|
|
pub dominant_second_flag_byte_hex: Option<String>,
|
|
pub dominant_second_flag_byte_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_flag_pair: Option<SmpSavePlacedStructureDynamicSideBufferShortProfileFlagPair>,
|
|
#[serde(default)]
|
|
pub sample_rows: Vec<SmpSavePlacedStructureDynamicSideBufferShortProfileFlagPairSample>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferShortProfileFlagPair {
|
|
pub first_flag_byte: u8,
|
|
pub first_flag_byte_hex: String,
|
|
pub second_flag_byte: u8,
|
|
pub second_flag_byte_hex: String,
|
|
pub count: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferShortProfileFlagPairSample {
|
|
pub sample_index: usize,
|
|
pub name_tag_relative_offset: usize,
|
|
#[serde(default)]
|
|
pub primary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub secondary_name: Option<String>,
|
|
pub first_flag_byte: u8,
|
|
pub first_flag_byte_hex: String,
|
|
pub second_flag_byte: u8,
|
|
pub second_flag_byte_hex: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferPayloadEnvelopeSample {
|
|
pub sample_index: usize,
|
|
pub name_tag_relative_offset: usize,
|
|
#[serde(default)]
|
|
pub primary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub secondary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub name_payload_end_relative_offset: Option<usize>,
|
|
#[serde(default)]
|
|
pub policy_tag_relative_offset: Option<usize>,
|
|
#[serde(default)]
|
|
pub profile_tag_relative_offset: Option<usize>,
|
|
#[serde(default)]
|
|
pub next_name_tag_relative_offset: Option<usize>,
|
|
#[serde(default)]
|
|
pub name_to_policy_gap_len: Option<usize>,
|
|
#[serde(default)]
|
|
pub policy_chunk_len: Option<usize>,
|
|
#[serde(default)]
|
|
pub profile_chunk_len_to_next_name_or_end: Option<usize>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidateSummary {
|
|
pub row_count_with_candidate_window: usize,
|
|
pub unique_candidate_pattern_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_child_count_candidate: Option<u16>,
|
|
pub dominant_child_count_candidate_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_saved_primary_child_byte_candidate: Option<u8>,
|
|
#[serde(default)]
|
|
pub dominant_saved_primary_child_byte_candidate_hex: Option<String>,
|
|
pub dominant_saved_primary_child_byte_candidate_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_candidate_pattern:
|
|
Option<SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern>,
|
|
#[serde(default)]
|
|
pub candidate_pattern_correlations:
|
|
Vec<SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePatternCorrelation>,
|
|
#[serde(default)]
|
|
pub profile_span_correlations:
|
|
Vec<SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanCorrelation>,
|
|
#[serde(default)]
|
|
pub compact_prefix_correlations:
|
|
Vec<SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixCorrelation>,
|
|
#[serde(default)]
|
|
pub sample_rows: Vec<SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidateSample>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern {
|
|
pub child_count_candidate: u16,
|
|
pub child_count_candidate_hex: String,
|
|
pub saved_primary_child_byte_candidate: u8,
|
|
pub saved_primary_child_byte_candidate_hex: String,
|
|
pub count: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidateSample {
|
|
pub sample_index: usize,
|
|
pub name_tag_relative_offset: usize,
|
|
#[serde(default)]
|
|
pub primary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub secondary_name: Option<String>,
|
|
pub child_count_candidate: u16,
|
|
pub child_count_candidate_hex: String,
|
|
pub saved_primary_child_byte_candidate: u8,
|
|
pub saved_primary_child_byte_candidate_hex: String,
|
|
#[serde(default)]
|
|
pub previous_profile_chunk_len_to_next_name_or_end: Option<usize>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixCorrelation {
|
|
pub prefix_leading_dword: u32,
|
|
pub prefix_leading_dword_hex: String,
|
|
pub prefix_trailing_word: u16,
|
|
pub prefix_trailing_word_hex: String,
|
|
pub prefix_separator_byte: u8,
|
|
pub prefix_separator_byte_hex: String,
|
|
pub row_count: usize,
|
|
pub unique_name_pair_count: usize,
|
|
pub unique_profile_span_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_primary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub dominant_secondary_name: Option<String>,
|
|
pub dominant_name_pair_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_profile_span: Option<usize>,
|
|
pub dominant_profile_span_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_candidate_pattern:
|
|
Option<SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern>,
|
|
#[serde(default)]
|
|
pub dominant_mode_family: Option<String>,
|
|
pub dominant_mode_family_count: usize,
|
|
#[serde(default)]
|
|
pub mode_family_counts: Vec<SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount>,
|
|
#[serde(default)]
|
|
pub name_pair_summaries:
|
|
Vec<SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanNamePairSummary>,
|
|
#[serde(default)]
|
|
pub profile_span_counts:
|
|
Vec<SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixProfileSpanCount>,
|
|
pub rows_with_previous_short_profile_flag_pair: usize,
|
|
#[serde(default)]
|
|
pub previous_short_profile_flag_pair_counts:
|
|
Vec<SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixFlagPairCount>,
|
|
#[serde(default)]
|
|
pub sample_rows: Vec<SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixSample>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixProfileSpanCount {
|
|
pub previous_profile_chunk_len_to_next_name_or_end: usize,
|
|
pub count: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixFlagPairCount {
|
|
pub first_flag_byte: u8,
|
|
pub first_flag_byte_hex: String,
|
|
pub second_flag_byte: u8,
|
|
pub second_flag_byte_hex: String,
|
|
pub count: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixSample {
|
|
pub sample_index: usize,
|
|
pub name_tag_relative_offset: usize,
|
|
#[serde(default)]
|
|
pub primary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub secondary_name: Option<String>,
|
|
pub child_count_candidate: u16,
|
|
pub child_count_candidate_hex: String,
|
|
pub saved_primary_child_byte_candidate: u8,
|
|
pub saved_primary_child_byte_candidate_hex: String,
|
|
#[serde(default)]
|
|
pub previous_profile_chunk_len_to_next_name_or_end: Option<usize>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePatternCorrelation {
|
|
pub child_count_candidate: u16,
|
|
pub child_count_candidate_hex: String,
|
|
pub saved_primary_child_byte_candidate: u8,
|
|
pub saved_primary_child_byte_candidate_hex: String,
|
|
pub row_count: usize,
|
|
pub unique_name_pair_count: usize,
|
|
pub unique_profile_span_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_primary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub dominant_secondary_name: Option<String>,
|
|
pub dominant_name_pair_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_profile_span: Option<usize>,
|
|
pub dominant_profile_span_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_mode_family: Option<String>,
|
|
pub dominant_mode_family_count: usize,
|
|
#[serde(default)]
|
|
pub mode_family_counts: Vec<SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount>,
|
|
#[serde(default)]
|
|
pub sample_rows: Vec<SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidateSample>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount {
|
|
pub mode_family: String,
|
|
pub count: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanCorrelation {
|
|
pub previous_profile_chunk_len_to_next_name_or_end: usize,
|
|
pub row_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_child_count_candidate: Option<u16>,
|
|
pub dominant_child_count_candidate_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_saved_primary_child_byte_candidate: Option<u8>,
|
|
#[serde(default)]
|
|
pub dominant_saved_primary_child_byte_candidate_hex: Option<String>,
|
|
pub dominant_saved_primary_child_byte_candidate_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_candidate_pattern:
|
|
Option<SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern>,
|
|
#[serde(default)]
|
|
pub dominant_mode_family: Option<String>,
|
|
pub dominant_mode_family_count: usize,
|
|
#[serde(default)]
|
|
pub mode_family_counts: Vec<SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount>,
|
|
#[serde(default)]
|
|
pub compact_prefix_pattern_summaries:
|
|
Vec<SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanPrefixSummary>,
|
|
#[serde(default)]
|
|
pub sample_rows: Vec<SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanSample>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanPrefixSummary {
|
|
pub prefix_leading_dword: u32,
|
|
pub prefix_leading_dword_hex: String,
|
|
pub prefix_trailing_word: u16,
|
|
pub prefix_trailing_word_hex: String,
|
|
pub prefix_separator_byte: u8,
|
|
pub prefix_separator_byte_hex: String,
|
|
pub count: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanSample {
|
|
pub sample_index: usize,
|
|
pub name_tag_relative_offset: usize,
|
|
#[serde(default)]
|
|
pub primary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub secondary_name: Option<String>,
|
|
pub prefix_leading_dword: u32,
|
|
pub prefix_leading_dword_hex: String,
|
|
pub prefix_trailing_word: u16,
|
|
pub prefix_trailing_word_hex: String,
|
|
pub prefix_separator_byte: u8,
|
|
pub prefix_separator_byte_hex: String,
|
|
pub child_count_candidate: u16,
|
|
pub child_count_candidate_hex: String,
|
|
pub saved_primary_child_byte_candidate: u8,
|
|
pub saved_primary_child_byte_candidate_hex: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferLiveEntryPreludeSummary {
|
|
pub live_entry_directory_row_count: usize,
|
|
pub decoded_live_entry_id_count: usize,
|
|
pub payload_relative_offset_monotonic: bool,
|
|
pub rows_with_payload_pointer_inside_records_span: usize,
|
|
pub rows_with_zero_child_count: usize,
|
|
pub rows_with_nonzero_child_count: usize,
|
|
pub rows_with_first_name_tag_after_prelude: usize,
|
|
pub rows_with_first_name_tag_at_offset_3: usize,
|
|
#[serde(default)]
|
|
pub unique_child_count_values: Vec<u16>,
|
|
#[serde(default)]
|
|
pub unique_first_name_tag_relative_offsets: Vec<usize>,
|
|
#[serde(default)]
|
|
pub dominant_child_count: Option<u16>,
|
|
pub dominant_child_count_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_saved_primary_child_byte: Option<u8>,
|
|
#[serde(default)]
|
|
pub dominant_saved_primary_child_byte_hex: Option<String>,
|
|
pub dominant_saved_primary_child_byte_count: usize,
|
|
#[serde(default)]
|
|
pub dominant_first_name_tag_relative_offset: Option<usize>,
|
|
pub dominant_first_name_tag_relative_offset_count: usize,
|
|
#[serde(default)]
|
|
pub sample_rows: Vec<SmpSavePlacedStructureDynamicSideBufferLiveEntryPreludeSample>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferLiveEntryPreludeSample {
|
|
pub sample_index: usize,
|
|
pub live_entry_id: u32,
|
|
pub payload_relative_offset: u32,
|
|
pub payload_relative_offset_hex: String,
|
|
pub payload_relative_to_records: usize,
|
|
pub child_count: u16,
|
|
pub child_count_hex: String,
|
|
pub saved_primary_child_byte: u8,
|
|
pub saved_primary_child_byte_hex: String,
|
|
pub first_payload_dword_hex: String,
|
|
#[serde(default)]
|
|
pub first_name_tag_relative_offset: Option<usize>,
|
|
#[serde(default)]
|
|
pub first_primary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub first_secondary_name: Option<String>,
|
|
#[serde(default)]
|
|
pub first_tertiary_name: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureDynamicSideBufferAlignmentProbe {
|
|
pub unique_side_buffer_name_pair_count: usize,
|
|
pub unique_triplet_name_pair_count: usize,
|
|
pub overlapping_name_pair_count: usize,
|
|
pub side_buffer_row_count: usize,
|
|
pub side_buffer_rows_with_matching_triplet_name_pair_count: usize,
|
|
pub side_buffer_rows_without_matching_triplet_name_pair_count: usize,
|
|
pub triplet_name_pairs_without_side_buffer_match_count: usize,
|
|
#[serde(default)]
|
|
pub matched_name_pair_samples: Vec<SmpSavePlacedStructureDynamicSideBufferNamePairSummary>,
|
|
#[serde(default)]
|
|
pub unmatched_side_buffer_name_pair_samples:
|
|
Vec<SmpSavePlacedStructureDynamicSideBufferNamePairSummary>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpRt3105SaveNameTableProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub semantic_alignment: Vec<String>,
|
|
pub header_offset: usize,
|
|
pub header_word_0: u32,
|
|
pub header_word_0_hex: String,
|
|
pub header_word_1: u32,
|
|
pub header_word_1_hex: String,
|
|
pub header_word_2: u32,
|
|
pub header_word_2_hex: String,
|
|
pub entry_stride: usize,
|
|
pub entry_stride_hex: String,
|
|
pub header_prefix_word_count: usize,
|
|
pub observed_entry_capacity: usize,
|
|
pub observed_entry_count: usize,
|
|
pub zero_trailer_entry_count: usize,
|
|
pub nonzero_trailer_entry_count: usize,
|
|
pub distinct_trailer_words: Vec<u32>,
|
|
pub distinct_trailer_hex_words: Vec<String>,
|
|
pub zero_trailer_entry_names: Vec<String>,
|
|
pub entries_offset: usize,
|
|
pub entries_end_offset: usize,
|
|
pub trailing_footer_hex: String,
|
|
pub footer_progress_word_0: u32,
|
|
pub footer_progress_word_0_hex: String,
|
|
pub footer_progress_word_1: u32,
|
|
pub footer_progress_word_1_hex: String,
|
|
pub footer_trailing_byte: u8,
|
|
pub footer_trailing_byte_hex: String,
|
|
pub footer_grounded_alignments: Vec<String>,
|
|
pub entries: Vec<SmpRt3105SaveNameTableEntry>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpRt3105SaveNamedLocomotiveAvailabilityProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub semantic_alignment: Vec<String>,
|
|
pub entries_offset: usize,
|
|
pub entry_stride: usize,
|
|
pub entry_stride_hex: String,
|
|
pub observed_entry_count: usize,
|
|
pub zero_availability_count: usize,
|
|
pub zero_availability_names: Vec<String>,
|
|
pub entries_end_offset: usize,
|
|
pub entries: Vec<SmpRt3105SaveNameTableEntry>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpRt3105SaveNameTableEntry {
|
|
pub index: usize,
|
|
pub offset: usize,
|
|
pub text: String,
|
|
pub availability_dword: u32,
|
|
pub availability_dword_hex: String,
|
|
pub trailer_word: u32,
|
|
pub trailer_word_hex: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSpecialConditionEntry {
|
|
pub slot_index: u8,
|
|
pub hidden: bool,
|
|
pub label_id: u32,
|
|
pub help_id: u32,
|
|
pub label: String,
|
|
pub value: u32,
|
|
pub value_hex: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSpecialConditionsProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub table_offset: usize,
|
|
pub table_len: usize,
|
|
pub enabled_visible_count: usize,
|
|
pub enabled_visible_labels: Vec<String>,
|
|
pub hidden_sentinel_slot_index: u8,
|
|
pub hidden_sentinel_value: u32,
|
|
pub hidden_sentinel_value_hex: String,
|
|
pub entries: Vec<SmpSpecialConditionEntry>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpAlignedRuntimeRuleBandLane {
|
|
pub band_index: usize,
|
|
pub absolute_offset: usize,
|
|
pub relative_offset: usize,
|
|
pub absolute_offset_hex: String,
|
|
pub relative_offset_hex: String,
|
|
pub lane_kind: String,
|
|
pub known_label: Option<String>,
|
|
pub value: u32,
|
|
pub value_hex: String,
|
|
pub probable_f32_le: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpAlignedRuntimeRuleBandProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub band_offset: usize,
|
|
pub band_end_offset: usize,
|
|
pub band_len: usize,
|
|
pub band_len_hex: String,
|
|
pub dword_count: usize,
|
|
pub known_editor_rule_dword_count: usize,
|
|
pub trailing_scalar_index: usize,
|
|
pub trailing_scalar_offset: usize,
|
|
pub trailing_scalar_offset_hex: String,
|
|
pub post_window_overlap_start_index: usize,
|
|
pub post_window_overlap_dword_count: usize,
|
|
pub post_window_overlap_end_index: usize,
|
|
pub post_window_overlap_post_relative_offset_start_hex: String,
|
|
pub post_window_overlap_post_relative_offset_end_hex: String,
|
|
pub nonzero_post_window_overlap_band_indices: Vec<usize>,
|
|
pub nonzero_post_window_overlap_post_relative_offset_hexes: Vec<String>,
|
|
pub nonzero_lane_count: usize,
|
|
pub nonzero_band_indices: Vec<usize>,
|
|
pub nonzero_relative_offset_hexes: Vec<String>,
|
|
pub nonzero_lanes: Vec<SmpAlignedRuntimeRuleBandLane>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpPostSpecialConditionsScalarLane {
|
|
pub absolute_offset: usize,
|
|
pub relative_offset: usize,
|
|
pub absolute_offset_hex: String,
|
|
pub relative_offset_hex: String,
|
|
pub value: u32,
|
|
pub value_hex: String,
|
|
pub probable_f32_le: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpPostTextGroundedFieldObservation {
|
|
pub field_name: String,
|
|
pub runtime_object_offset: usize,
|
|
pub runtime_object_offset_hex: String,
|
|
pub file_offset: usize,
|
|
pub file_offset_hex: String,
|
|
pub field_width_bytes: usize,
|
|
pub field_width_bytes_hex: String,
|
|
pub raw_hex: String,
|
|
pub value_u8: Option<u8>,
|
|
pub value_u8_hex: Option<String>,
|
|
pub value_u32: Option<u32>,
|
|
pub value_u32_hex: Option<String>,
|
|
pub probable_f32_le: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpPostTextFloatAlignmentCandidate {
|
|
pub grounded_field_name: String,
|
|
pub grounded_field_runtime_object_offset: usize,
|
|
pub grounded_field_runtime_object_offset_hex: String,
|
|
pub grounded_field_file_offset: usize,
|
|
pub grounded_field_file_offset_hex: String,
|
|
pub candidate_offset: usize,
|
|
pub candidate_offset_hex: String,
|
|
pub candidate_value: u32,
|
|
pub candidate_value_hex: String,
|
|
pub probable_f32_le: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpPostTextFieldNeighborhoodProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub window_offset: usize,
|
|
pub window_end_offset: usize,
|
|
pub window_len: usize,
|
|
pub window_len_hex: String,
|
|
pub grounded_field_observations: Vec<SmpPostTextGroundedFieldObservation>,
|
|
pub one_byte_early_float_candidates: Vec<SmpPostTextFloatAlignmentCandidate>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLocomotivePolicyFieldObservation {
|
|
pub field_name: String,
|
|
pub runtime_object_offset: usize,
|
|
pub runtime_object_offset_hex: String,
|
|
pub file_offset: usize,
|
|
pub file_offset_hex: String,
|
|
pub field_width_bytes: usize,
|
|
pub field_width_bytes_hex: String,
|
|
pub raw_hex: String,
|
|
pub value_u8: Option<u8>,
|
|
pub value_u8_hex: Option<String>,
|
|
pub value_u32: Option<u32>,
|
|
pub value_u32_hex: Option<String>,
|
|
pub probable_f32_le: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLocomotivePolicyFloatAlignmentCandidate {
|
|
pub grounded_field_name: String,
|
|
pub grounded_field_runtime_object_offset: usize,
|
|
pub grounded_field_runtime_object_offset_hex: String,
|
|
pub grounded_field_file_offset: usize,
|
|
pub grounded_field_file_offset_hex: String,
|
|
pub candidate_offset: usize,
|
|
pub candidate_offset_hex: String,
|
|
pub candidate_value: u32,
|
|
pub candidate_value_hex: String,
|
|
pub probable_f32_le: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLocomotivePolicyNeighborhoodProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub window_offset: usize,
|
|
pub window_end_offset: usize,
|
|
pub window_len: usize,
|
|
pub window_len_hex: String,
|
|
pub grounded_field_observations: Vec<SmpLocomotivePolicyFieldObservation>,
|
|
pub three_byte_early_float_candidates: Vec<SmpLocomotivePolicyFloatAlignmentCandidate>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpPreRecipeScalarPlateauLane {
|
|
pub absolute_offset: usize,
|
|
pub relative_offset: usize,
|
|
pub absolute_offset_hex: String,
|
|
pub relative_offset_hex: String,
|
|
pub value: u32,
|
|
pub value_hex: String,
|
|
pub probable_f32_le: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpPreRecipeScalarPlateauProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub window_offset: usize,
|
|
pub window_end_offset: usize,
|
|
pub window_len: usize,
|
|
pub window_len_hex: String,
|
|
pub aligned_dword_count: usize,
|
|
pub family_signature: String,
|
|
pub nonzero_lanes: Vec<SmpPreRecipeScalarPlateauLane>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpRecipeBookSummaryBook {
|
|
pub book_index: usize,
|
|
pub book_offset: usize,
|
|
pub book_offset_hex: String,
|
|
pub head_kind: String,
|
|
pub head_nonzero_byte_count: usize,
|
|
pub head_cdcd_byte_count: usize,
|
|
pub head_first_16_hex: String,
|
|
pub max_annual_production_offset: usize,
|
|
pub max_annual_production_offset_hex: String,
|
|
pub max_annual_production_word: u32,
|
|
pub max_annual_production_word_hex: String,
|
|
pub max_annual_production_probable_f32_le: Option<String>,
|
|
pub line_area_offset: usize,
|
|
pub line_area_offset_hex: String,
|
|
pub line_area_len: usize,
|
|
pub line_area_len_hex: String,
|
|
pub line_area_kind: String,
|
|
pub line_area_nonzero_byte_count: usize,
|
|
pub line_area_cdcd_byte_count: usize,
|
|
pub line_area_first_16_hex: String,
|
|
pub lines: Vec<SmpRecipeBookLineSummary>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpRecipeBookLineSummary {
|
|
pub line_index: usize,
|
|
pub line_offset: usize,
|
|
pub line_offset_hex: String,
|
|
pub line_kind: String,
|
|
pub line_signature_kind: String,
|
|
pub imports_to_runtime_descriptor: bool,
|
|
pub runtime_import_branch_kind: String,
|
|
pub line_nonzero_byte_count: usize,
|
|
pub line_cdcd_byte_count: usize,
|
|
pub line_first_16_hex: String,
|
|
pub mode_word_offset: usize,
|
|
pub mode_word_offset_hex: String,
|
|
pub mode_word: u32,
|
|
pub mode_word_hex: String,
|
|
pub annual_amount_offset: usize,
|
|
pub annual_amount_offset_hex: String,
|
|
pub annual_amount_word: u32,
|
|
pub annual_amount_word_hex: String,
|
|
pub annual_amount_probable_f32_le: Option<String>,
|
|
pub supplied_cargo_token_offset: usize,
|
|
pub supplied_cargo_token_offset_hex: String,
|
|
pub supplied_cargo_token_word: u32,
|
|
pub supplied_cargo_token_word_hex: String,
|
|
pub supplied_cargo_token_layout_kind: String,
|
|
pub supplied_cargo_token_window_hex: String,
|
|
pub supplied_cargo_token_window_ascii: String,
|
|
pub supplied_cargo_token_active_in_runtime_import: bool,
|
|
pub supplied_cargo_token_probable_high16_ascii_stem: Option<String>,
|
|
pub demanded_cargo_token_offset: usize,
|
|
pub demanded_cargo_token_offset_hex: String,
|
|
pub demanded_cargo_token_word: u32,
|
|
pub demanded_cargo_token_word_hex: String,
|
|
pub demanded_cargo_token_layout_kind: String,
|
|
pub demanded_cargo_token_window_hex: String,
|
|
pub demanded_cargo_token_window_ascii: String,
|
|
pub demanded_cargo_token_active_in_runtime_import: bool,
|
|
pub demanded_cargo_token_probable_high16_ascii_stem: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpRecipeBookSummaryProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub root_offset: usize,
|
|
pub root_offset_hex: String,
|
|
pub runtime_object_root_offset: usize,
|
|
pub runtime_object_root_offset_hex: String,
|
|
pub book_count: usize,
|
|
pub book_stride: usize,
|
|
pub book_stride_hex: String,
|
|
pub max_annual_production_relative_offset: usize,
|
|
pub max_annual_production_relative_offset_hex: String,
|
|
pub line_area_relative_offset: usize,
|
|
pub line_area_relative_offset_hex: String,
|
|
pub line_count: usize,
|
|
pub line_stride: usize,
|
|
pub line_stride_hex: String,
|
|
pub books: Vec<SmpRecipeBookSummaryBook>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpPostSpecialConditionsScalarProbe {
|
|
pub profile_family: String,
|
|
pub source_kind: String,
|
|
pub window_offset: usize,
|
|
pub window_end_offset: usize,
|
|
pub window_len: usize,
|
|
pub window_len_hex: String,
|
|
pub dword_count: usize,
|
|
pub overlap_end_offset: usize,
|
|
pub overlap_end_offset_hex: String,
|
|
pub overlap_dword_count: usize,
|
|
pub overlap_nonzero_dword_count: usize,
|
|
pub overlap_nonzero_relative_offset_hexes: Vec<String>,
|
|
pub tail_offset: usize,
|
|
pub tail_offset_hex: String,
|
|
pub tail_len: usize,
|
|
pub tail_len_hex: String,
|
|
pub tail_dword_count: usize,
|
|
pub tail_runtime_object_offset: usize,
|
|
pub tail_runtime_object_offset_hex: String,
|
|
pub tail_runtime_object_end_offset: usize,
|
|
pub tail_runtime_object_end_offset_hex: String,
|
|
pub tail_runtime_object_validated_byte_mirror: bool,
|
|
pub tail_grounded_live_field_offset: usize,
|
|
pub tail_grounded_live_field_offset_hex: String,
|
|
pub tail_grounded_live_field_name: String,
|
|
pub tail_grounded_live_field_copy_len: usize,
|
|
pub tail_grounded_live_field_copy_len_hex: String,
|
|
pub tail_grounded_live_field_copy_end_offset: usize,
|
|
pub tail_grounded_live_field_copy_end_offset_hex: String,
|
|
pub tail_window_cuts_through_grounded_live_field: bool,
|
|
pub tail_grounded_live_field_remaining_file_window_offset: usize,
|
|
pub tail_grounded_live_field_remaining_file_window_offset_hex: String,
|
|
pub tail_grounded_live_field_remaining_file_window_len: usize,
|
|
pub tail_grounded_live_field_remaining_file_window_len_hex: String,
|
|
pub tail_grounded_live_field_remaining_file_window_nonzero_byte_count: usize,
|
|
pub tail_grounded_live_field_remaining_file_window_first_nonzero_offset: Option<usize>,
|
|
pub tail_grounded_live_field_remaining_file_window_first_nonzero_offset_hex: Option<String>,
|
|
pub tail_grounded_live_field_remaining_file_window_last_nonzero_offset: Option<usize>,
|
|
pub tail_grounded_live_field_remaining_file_window_last_nonzero_offset_hex: Option<String>,
|
|
pub tail_next_grounded_dword_field_offset: usize,
|
|
pub tail_next_grounded_dword_field_offset_hex: String,
|
|
pub tail_next_grounded_dword_field_file_offset: usize,
|
|
pub tail_next_grounded_dword_field_file_offset_hex: String,
|
|
pub tail_second_grounded_dword_field_offset: usize,
|
|
pub tail_second_grounded_dword_field_offset_hex: String,
|
|
pub tail_second_grounded_dword_field_file_offset: usize,
|
|
pub tail_second_grounded_dword_field_file_offset_hex: String,
|
|
pub post_text_field_file_alignment_matches_grounded_dword_fields: bool,
|
|
pub tail_nonzero_dword_count: usize,
|
|
pub tail_first_nonzero_offset: Option<usize>,
|
|
pub tail_first_nonzero_offset_hex: Option<String>,
|
|
pub tail_last_nonzero_offset: Option<usize>,
|
|
pub tail_last_nonzero_offset_hex: Option<String>,
|
|
pub tail_nonzero_relative_offset_hexes: Vec<String>,
|
|
pub nonzero_dword_count: usize,
|
|
pub first_nonzero_offset: Option<usize>,
|
|
pub first_nonzero_offset_hex: Option<String>,
|
|
pub last_nonzero_offset: Option<usize>,
|
|
pub last_nonzero_offset_hex: Option<String>,
|
|
pub nonzero_lanes: Vec<SmpPostSpecialConditionsScalarLane>,
|
|
pub evidence: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpClassicRehydrateProfileProbe {
|
|
pub profile_family: String,
|
|
pub progress_32dc_offset: usize,
|
|
pub progress_3714_offset: usize,
|
|
pub progress_3715_offset: usize,
|
|
pub packed_profile_offset: usize,
|
|
pub packed_profile_len: usize,
|
|
pub packed_profile_len_hex: String,
|
|
pub packed_profile_block: SmpClassicPackedProfileBlock,
|
|
pub ascii_runs: Vec<SmpAsciiPreview>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpClassicPackedProfileBlock {
|
|
pub relative_len: usize,
|
|
pub relative_len_hex: String,
|
|
pub leading_word_0: u32,
|
|
pub leading_word_0_hex: String,
|
|
pub trailing_zero_word_count_after_leading_word: usize,
|
|
pub map_path_offset: usize,
|
|
pub map_path: Option<String>,
|
|
pub display_name_offset: usize,
|
|
pub display_name: Option<String>,
|
|
pub profile_byte_0x77: u8,
|
|
pub profile_byte_0x77_hex: String,
|
|
pub profile_byte_0x82: u8,
|
|
pub profile_byte_0x82_hex: String,
|
|
pub profile_byte_0x97: u8,
|
|
pub profile_byte_0x97_hex: String,
|
|
pub profile_byte_0xc5: u8,
|
|
pub profile_byte_0xc5_hex: String,
|
|
pub stable_nonzero_words: Vec<SmpPackedProfileWordLane>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpRt3105PackedProfileProbe {
|
|
pub profile_family: String,
|
|
pub packed_profile_offset: usize,
|
|
pub packed_profile_len: usize,
|
|
pub packed_profile_len_hex: String,
|
|
pub packed_profile_block: SmpRt3105PackedProfileBlock,
|
|
pub ascii_runs: Vec<SmpAsciiPreview>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpRt3105PackedProfileBlock {
|
|
pub relative_len: usize,
|
|
pub relative_len_hex: String,
|
|
pub leading_word_0: u32,
|
|
pub leading_word_0_hex: String,
|
|
pub trailing_zero_word_count_after_leading_word: usize,
|
|
pub header_flag_word_3: u32,
|
|
pub header_flag_word_3_hex: String,
|
|
pub map_path_offset: usize,
|
|
pub map_path: Option<String>,
|
|
pub display_name_offset: usize,
|
|
pub display_name: Option<String>,
|
|
pub profile_byte_0x77: u8,
|
|
pub profile_byte_0x77_hex: String,
|
|
pub profile_byte_0x82: u8,
|
|
pub profile_byte_0x82_hex: String,
|
|
pub profile_byte_0x97: u8,
|
|
pub profile_byte_0x97_hex: String,
|
|
pub profile_byte_0xc5: u8,
|
|
pub profile_byte_0xc5_hex: String,
|
|
pub stable_nonzero_words: Vec<SmpPackedProfileWordLane>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveLoadCandidateTableSummary {
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub observed_entry_count: usize,
|
|
pub zero_availability_count: usize,
|
|
pub zero_availability_names: Vec<String>,
|
|
pub footer_progress_hex_words: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveLoadSummary {
|
|
pub file_extension_hint: Option<String>,
|
|
pub container_profile_family: Option<String>,
|
|
pub mechanism_family: String,
|
|
pub mechanism_confidence: String,
|
|
pub packed_profile_kind: Option<String>,
|
|
pub packed_profile_family: Option<String>,
|
|
pub packed_profile_offset: Option<usize>,
|
|
pub packed_profile_len: Option<usize>,
|
|
pub map_path: Option<String>,
|
|
pub display_name: Option<String>,
|
|
pub profile_byte_0x77: Option<u8>,
|
|
pub profile_byte_0x77_hex: Option<String>,
|
|
pub profile_byte_0x82: Option<u8>,
|
|
pub profile_byte_0x82_hex: Option<String>,
|
|
pub profile_byte_0x97: Option<u8>,
|
|
pub profile_byte_0x97_hex: Option<String>,
|
|
pub profile_byte_0xc5: Option<u8>,
|
|
pub profile_byte_0xc5_hex: Option<String>,
|
|
pub trailer_family: Option<String>,
|
|
pub bridge_family: Option<String>,
|
|
pub candidate_table: Option<SmpSaveLoadCandidateTableSummary>,
|
|
pub notes: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedProfile {
|
|
pub profile_kind: String,
|
|
pub profile_family: String,
|
|
pub packed_profile_offset: usize,
|
|
pub packed_profile_len: usize,
|
|
pub packed_profile_len_hex: String,
|
|
pub leading_word_0: u32,
|
|
pub leading_word_0_hex: String,
|
|
pub header_flag_word_3: Option<u32>,
|
|
pub header_flag_word_3_hex: Option<String>,
|
|
pub map_path: Option<String>,
|
|
pub display_name: Option<String>,
|
|
pub profile_byte_0x77: u8,
|
|
pub profile_byte_0x77_hex: String,
|
|
pub profile_byte_0x82: u8,
|
|
pub profile_byte_0x82_hex: String,
|
|
pub profile_byte_0x97: u8,
|
|
pub profile_byte_0x97_hex: String,
|
|
pub profile_byte_0xc5: u8,
|
|
pub profile_byte_0xc5_hex: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedCandidateAvailabilityTable {
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub header_offset: usize,
|
|
pub entries_offset: usize,
|
|
pub entries_end_offset: usize,
|
|
pub observed_entry_count: usize,
|
|
pub zero_availability_count: usize,
|
|
pub zero_availability_names: Vec<String>,
|
|
pub footer_progress_hex_words: Vec<String>,
|
|
pub entries: Vec<SmpRt3105SaveNameTableEntry>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedNamedLocomotiveAvailabilityTable {
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
#[serde(default)]
|
|
pub header_offset: Option<usize>,
|
|
#[serde(default)]
|
|
pub entries_offset: Option<usize>,
|
|
#[serde(default)]
|
|
pub entries_end_offset: Option<usize>,
|
|
pub observed_entry_count: usize,
|
|
pub zero_availability_count: usize,
|
|
pub zero_availability_names: Vec<String>,
|
|
pub entries: Vec<SmpRt3105SaveNameTableEntry>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedLocomotiveCatalogEntry {
|
|
pub locomotive_id: u32,
|
|
pub name: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedLocomotiveCatalog {
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
#[serde(default)]
|
|
pub entries_offset: Option<usize>,
|
|
pub observed_entry_count: usize,
|
|
pub entries: Vec<SmpLoadedLocomotiveCatalogEntry>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedCargoCatalogEntry {
|
|
pub slot_id: u32,
|
|
pub label: String,
|
|
#[serde(default)]
|
|
pub cargo_class: RuntimeCargoClass,
|
|
pub book_index: usize,
|
|
pub max_annual_production_word: u32,
|
|
pub mode_word: u32,
|
|
pub runtime_import_branch_kind: String,
|
|
pub annual_amount_word: u32,
|
|
pub supplied_cargo_token_word: u32,
|
|
#[serde(default)]
|
|
pub supplied_cargo_token_probable_high16_ascii_stem: Option<String>,
|
|
pub demanded_cargo_token_word: u32,
|
|
#[serde(default)]
|
|
pub demanded_cargo_token_probable_high16_ascii_stem: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedCargoCatalog {
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
#[serde(default)]
|
|
pub root_offset: Option<usize>,
|
|
pub observed_entry_count: usize,
|
|
pub entries: Vec<SmpLoadedCargoCatalogEntry>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedWorldIssue37State {
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub issue_value: u32,
|
|
pub issue_value_hex: String,
|
|
pub issue_38_value: u32,
|
|
pub issue_38_value_hex: String,
|
|
pub issue_39_value: u32,
|
|
pub issue_39_value_hex: String,
|
|
pub issue_3a_value: u32,
|
|
pub issue_3a_value_hex: String,
|
|
pub multiplier_raw_u32: u32,
|
|
pub multiplier_raw_hex: String,
|
|
pub multiplier_value_f32_text: String,
|
|
#[serde(default)]
|
|
pub issue_opinion_base_terms_raw_i32: Vec<i32>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedWorldEconomicTuningState {
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub mirror_raw_u32: u32,
|
|
pub mirror_raw_hex: String,
|
|
pub mirror_value_f32_text: String,
|
|
pub lane_raw_u32: Vec<u32>,
|
|
pub lane_raw_hex: Vec<String>,
|
|
pub lane_value_f32_text: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedWorldFinanceNeighborhoodState {
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub packed_year_word_raw_u16: u16,
|
|
pub packed_year_word_raw_hex: String,
|
|
pub partial_year_progress_raw_u8: u8,
|
|
pub partial_year_progress_raw_hex: String,
|
|
pub current_calendar_tuple_word_raw_u32: u32,
|
|
pub current_calendar_tuple_word_raw_hex: String,
|
|
pub current_calendar_tuple_word_2_raw_u32: u32,
|
|
pub current_calendar_tuple_word_2_raw_hex: String,
|
|
pub absolute_counter_raw_u32: u32,
|
|
pub absolute_counter_raw_hex: String,
|
|
pub absolute_counter_mirror_raw_u32: u32,
|
|
pub absolute_counter_mirror_raw_hex: String,
|
|
pub stock_policy_raw_u8: u8,
|
|
pub stock_policy_raw_hex: String,
|
|
pub bond_policy_raw_u8: u8,
|
|
pub bond_policy_raw_hex: String,
|
|
pub bankruptcy_policy_raw_u8: u8,
|
|
pub bankruptcy_policy_raw_hex: String,
|
|
pub dividend_policy_raw_u8: u8,
|
|
pub dividend_policy_raw_hex: String,
|
|
pub building_density_growth_setting_raw_u32: u32,
|
|
pub building_density_growth_setting_raw_hex: String,
|
|
pub labels: Vec<String>,
|
|
pub relative_offsets: Vec<usize>,
|
|
pub relative_offset_hex: Vec<String>,
|
|
pub raw_u32: Vec<u32>,
|
|
pub raw_hex: Vec<String>,
|
|
pub value_i32: Vec<i32>,
|
|
pub value_f32_text: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedWorldLocomotivePolicyState {
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
#[serde(default)]
|
|
pub selected_year_gap_scalar_raw_u32: Option<u32>,
|
|
#[serde(default)]
|
|
pub selected_year_gap_scalar_raw_hex: Option<String>,
|
|
#[serde(default)]
|
|
pub selected_year_gap_scalar_value_f32_text: Option<String>,
|
|
#[serde(default)]
|
|
pub linked_site_removal_follow_on_gate_raw_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub linked_site_removal_follow_on_gate_raw_hex: Option<String>,
|
|
#[serde(default)]
|
|
pub auto_show_grade_during_track_lay_raw_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub auto_show_grade_during_track_lay_raw_hex: Option<String>,
|
|
#[serde(default)]
|
|
pub starting_building_density_level_raw_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub starting_building_density_level_raw_hex: Option<String>,
|
|
#[serde(default)]
|
|
pub building_density_growth_raw_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub building_density_growth_raw_hex: Option<String>,
|
|
#[serde(default)]
|
|
pub leftover_simulation_time_accumulator_raw_u32: Option<u32>,
|
|
#[serde(default)]
|
|
pub leftover_simulation_time_accumulator_raw_hex: Option<String>,
|
|
#[serde(default)]
|
|
pub leftover_simulation_time_accumulator_value_f32_text: Option<String>,
|
|
#[serde(default)]
|
|
pub selected_year_lane_snapshot_raw_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub selected_year_lane_snapshot_raw_hex: Option<String>,
|
|
#[serde(default)]
|
|
pub all_steam_locomotives_available_raw_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub all_steam_locomotives_available_raw_hex: Option<String>,
|
|
#[serde(default)]
|
|
pub all_diesel_locomotives_available_raw_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub all_diesel_locomotives_available_raw_hex: Option<String>,
|
|
#[serde(default)]
|
|
pub all_electric_locomotives_available_raw_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub all_electric_locomotives_available_raw_hex: Option<String>,
|
|
#[serde(default)]
|
|
pub cached_available_locomotive_rating_raw_u32: Option<u32>,
|
|
#[serde(default)]
|
|
pub cached_available_locomotive_rating_raw_hex: Option<String>,
|
|
#[serde(default)]
|
|
pub cached_available_locomotive_rating_value_f32_text: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedCompanyRosterEntry {
|
|
pub company_id: u32,
|
|
pub active: bool,
|
|
#[serde(default)]
|
|
pub controller_kind: RuntimeCompanyControllerKind,
|
|
pub current_cash: i64,
|
|
pub debt: u64,
|
|
#[serde(default)]
|
|
pub credit_rating_score: Option<i64>,
|
|
#[serde(default)]
|
|
pub prime_rate: Option<i64>,
|
|
#[serde(default)]
|
|
pub available_track_laying_capacity: Option<u32>,
|
|
#[serde(default)]
|
|
pub track_piece_counts: RuntimeTrackPieceCounts,
|
|
#[serde(default)]
|
|
pub linked_chairman_profile_id: Option<u32>,
|
|
#[serde(default)]
|
|
pub book_value_per_share: i64,
|
|
#[serde(default)]
|
|
pub investor_confidence: i64,
|
|
#[serde(default)]
|
|
pub management_attitude: i64,
|
|
#[serde(default)]
|
|
pub takeover_cooldown_year: Option<u32>,
|
|
#[serde(default)]
|
|
pub merger_cooldown_year: Option<u32>,
|
|
#[serde(default)]
|
|
pub preferred_locomotive_engine_type_raw_u8: Option<u8>,
|
|
#[serde(default)]
|
|
pub market_state: Option<RuntimeCompanyMarketState>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedCompanyRoster {
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub observed_entry_count: usize,
|
|
#[serde(default)]
|
|
pub selected_company_id: Option<u32>,
|
|
pub entries: Vec<SmpLoadedCompanyRosterEntry>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedChairmanProfileEntry {
|
|
pub profile_id: u32,
|
|
pub name: String,
|
|
pub active: bool,
|
|
#[serde(default)]
|
|
pub current_cash: i64,
|
|
#[serde(default)]
|
|
pub linked_company_id: Option<u32>,
|
|
#[serde(default)]
|
|
pub company_holdings: BTreeMap<u32, u32>,
|
|
#[serde(default)]
|
|
pub holdings_value_total: i64,
|
|
#[serde(default)]
|
|
pub net_worth_total: i64,
|
|
#[serde(default)]
|
|
pub purchasing_power_total: i64,
|
|
#[serde(default)]
|
|
pub personality_byte_0x291: Option<u8>,
|
|
#[serde(default)]
|
|
pub issue_opinion_terms_raw_i32: Vec<i32>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedChairmanProfileTable {
|
|
pub source_kind: String,
|
|
pub semantic_family: String,
|
|
pub observed_entry_count: usize,
|
|
#[serde(default)]
|
|
pub selected_chairman_profile_id: Option<u32>,
|
|
pub entries: Vec<SmpLoadedChairmanProfileEntry>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpSaveScalarCandidate {
|
|
pub relative_offset: usize,
|
|
pub relative_offset_hex: String,
|
|
pub raw_u64: u64,
|
|
pub raw_u64_hex: String,
|
|
pub value_i64: i64,
|
|
pub value_f64: f64,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpSaveDwordCandidate {
|
|
pub label: String,
|
|
pub relative_offset: usize,
|
|
pub relative_offset_hex: String,
|
|
pub raw_u32: u32,
|
|
pub raw_u32_hex: String,
|
|
pub value_i32: i32,
|
|
pub value_f32: f32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveWorldSelectionRoleAnalysisEntry {
|
|
pub slot_index: usize,
|
|
pub selector_byte: u8,
|
|
pub selector_byte_hex: String,
|
|
pub role_gate_byte: u8,
|
|
pub role_gate_byte_hex: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSaveWorldSelectionRoleAnalysis {
|
|
pub selected_company_id: u32,
|
|
pub selected_chairman_profile_id: u32,
|
|
pub campaign_override_flag: u8,
|
|
pub campaign_override_flag_hex: String,
|
|
pub chairman_slots: Vec<SmpSaveWorldSelectionRoleAnalysisEntry>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpSaveCompanyRecordAnalysisEntry {
|
|
pub company_id: u32,
|
|
pub name: String,
|
|
pub active: bool,
|
|
#[serde(default)]
|
|
pub linked_chairman_profile_id: Option<u32>,
|
|
pub outstanding_shares: u32,
|
|
pub debt: u64,
|
|
pub bond_count: u8,
|
|
#[serde(default)]
|
|
pub live_bond_slots: Vec<crate::RuntimeCompanyBondSlot>,
|
|
#[serde(default)]
|
|
pub largest_live_bond_principal: Option<u32>,
|
|
#[serde(default)]
|
|
pub highest_coupon_live_bond_principal: Option<u32>,
|
|
#[serde(default)]
|
|
pub available_track_laying_capacity: Option<u32>,
|
|
pub company_value_scalar_f32: f32,
|
|
pub cached_share_support_scalar_f32: f32,
|
|
pub cached_share_price_f32: f32,
|
|
pub chairman_salary_baseline: u32,
|
|
pub chairman_salary_current: u32,
|
|
pub chairman_bonus_year: u32,
|
|
pub chairman_bonus_amount: i32,
|
|
pub founding_year: u32,
|
|
pub last_bankruptcy_year: u32,
|
|
pub last_dividend_year: u32,
|
|
pub preferred_locomotive_engine_type_raw_u8: u8,
|
|
pub preferred_locomotive_engine_type_raw_hex: String,
|
|
pub city_connection_latch: bool,
|
|
pub linked_transit_latch: bool,
|
|
pub linked_transit_autoroute_site_score_cache_refresh_absolute_counter: u32,
|
|
pub linked_transit_site_peer_cache_refresh_absolute_counter: u32,
|
|
#[serde(default)]
|
|
pub linked_transit_route_anchor_entry_id: Option<u32>,
|
|
#[serde(default)]
|
|
pub linked_transit_route_anchor_fallback_counts: Vec<u32>,
|
|
pub merger_cooldown_year: u32,
|
|
pub takeover_cooldown_year: u32,
|
|
#[serde(default)]
|
|
pub scalar_dword_candidates: Vec<SmpSaveDwordCandidate>,
|
|
#[serde(default)]
|
|
pub post_capacity_dword_candidates: Vec<SmpSaveDwordCandidate>,
|
|
#[serde(default)]
|
|
pub stat_band_root_0cfb_candidates: Vec<SmpSaveDwordCandidate>,
|
|
#[serde(default)]
|
|
pub stat_band_root_0d7f_candidates: Vec<SmpSaveDwordCandidate>,
|
|
#[serde(default)]
|
|
pub stat_band_root_1c47_candidates: Vec<SmpSaveDwordCandidate>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpSaveChairmanRecordAnalysisEntry {
|
|
pub profile_id: u32,
|
|
pub name: String,
|
|
pub active: bool,
|
|
pub current_cash: f64,
|
|
#[serde(default)]
|
|
pub linked_company_id: Option<u32>,
|
|
#[serde(default)]
|
|
pub holdings_by_company: BTreeMap<u32, u32>,
|
|
#[serde(default)]
|
|
pub derived_holdings_share_price_total: Option<i64>,
|
|
#[serde(default)]
|
|
pub derived_net_worth_share_price_total: Option<i64>,
|
|
#[serde(default)]
|
|
pub derived_cached_purchasing_power_total: Option<i64>,
|
|
pub personality_byte_0x291: u8,
|
|
pub personality_byte_0x291_hex: String,
|
|
#[serde(default)]
|
|
pub cached_scalar_candidates: Vec<SmpSaveScalarCandidate>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpSaveCompanyChairmanAnalysisReport {
|
|
pub profile_family: String,
|
|
#[serde(default)]
|
|
pub selected_company_id: Option<u32>,
|
|
#[serde(default)]
|
|
pub selected_chairman_profile_id: Option<u32>,
|
|
#[serde(default)]
|
|
pub world_selection_context: Option<SmpSaveWorldSelectionRoleAnalysis>,
|
|
#[serde(default)]
|
|
pub world_issue_37: Option<SmpSaveWorldIssue37Probe>,
|
|
#[serde(default)]
|
|
pub world_economic_tuning: Option<SmpSaveWorldEconomicTuningProbe>,
|
|
#[serde(default)]
|
|
pub world_finance_neighborhood: Option<SmpSaveWorldFinanceNeighborhoodProbe>,
|
|
#[serde(default)]
|
|
pub train_collection_header: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
|
#[serde(default)]
|
|
pub train_collection_directory: Option<SmpSaveTrainCollectionDirectoryProbe>,
|
|
#[serde(default)]
|
|
pub region_collection_header: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
|
#[serde(default)]
|
|
pub region_record_triplets: Option<SmpSaveRegionRecordTripletProbe>,
|
|
#[serde(default)]
|
|
pub region_queued_notice_records: Option<SmpSaveRegionQueuedNoticeRecordProbe>,
|
|
#[serde(default)]
|
|
pub region_fixed_row_run_candidates: Option<SmpSaveRegionFixedRowRunCandidateProbe>,
|
|
#[serde(default)]
|
|
pub placed_structure_collection_header: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
|
#[serde(default)]
|
|
pub placed_structure_record_triplets: Option<SmpSavePlacedStructureRecordTripletProbe>,
|
|
#[serde(default)]
|
|
pub placed_structure_dynamic_side_buffer: Option<SmpSavePlacedStructureDynamicSideBufferProbe>,
|
|
#[serde(default)]
|
|
pub placed_structure_dynamic_side_buffer_alignment:
|
|
Option<SmpSavePlacedStructureDynamicSideBufferAlignmentProbe>,
|
|
#[serde(default)]
|
|
pub unclassified_tagged_collection_headers: Vec<SmpSaveUnclassifiedTaggedCollectionHeaderProbe>,
|
|
#[serde(default)]
|
|
pub company_entries: Vec<SmpSaveCompanyRecordAnalysisEntry>,
|
|
#[serde(default)]
|
|
pub chairman_entries: Vec<SmpSaveChairmanRecordAnalysisEntry>,
|
|
#[serde(default)]
|
|
pub notes: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpServiceTraceBranchStatus {
|
|
pub branch_name: String,
|
|
pub status: String,
|
|
#[serde(default)]
|
|
pub grounded_inputs: Vec<String>,
|
|
#[serde(default)]
|
|
pub blocking_inputs: Vec<String>,
|
|
#[serde(default)]
|
|
pub candidate_consumers: Vec<String>,
|
|
#[serde(default)]
|
|
pub notes: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpPeriodicCompanyServiceTraceEntry {
|
|
pub company_id: u32,
|
|
pub name: String,
|
|
pub active: bool,
|
|
#[serde(default)]
|
|
pub linked_chairman_profile_id: Option<u32>,
|
|
pub preferred_locomotive_engine_type_raw_u8: u8,
|
|
pub city_connection_latch: bool,
|
|
pub linked_transit_latch: bool,
|
|
pub linked_transit_autoroute_site_score_cache_refresh_absolute_counter: u32,
|
|
pub linked_transit_site_peer_cache_refresh_absolute_counter: u32,
|
|
#[serde(default)]
|
|
pub branches: Vec<SmpServiceTraceBranchStatus>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureProfilePayloadSummaryEntry {
|
|
pub profile_payload_dword_hex: String,
|
|
pub profile_status_kind: String,
|
|
pub count: usize,
|
|
#[serde(default)]
|
|
pub sample_primary_names: Vec<String>,
|
|
#[serde(default)]
|
|
pub sample_secondary_names: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureProfilePayloadDeltaSummaryEntry {
|
|
pub delta_hex: String,
|
|
pub count: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureProfileFooterPaddingSummaryEntry {
|
|
pub padding_len: usize,
|
|
pub count: usize,
|
|
#[serde(default)]
|
|
pub sample_hex_bytes: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureProfileCompanionByteSummaryEntry {
|
|
pub companion_byte_hex: String,
|
|
pub count: usize,
|
|
#[serde(default)]
|
|
pub sample_primary_names: Vec<String>,
|
|
#[serde(default)]
|
|
pub sample_secondary_names: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructureNonzeroCompanionNamePairSummaryEntry {
|
|
pub companion_byte_hex: String,
|
|
pub primary_name: String,
|
|
pub secondary_name: String,
|
|
pub count: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpSavePlacedStructurePolicyTrailingWordSummaryEntry {
|
|
pub policy_trailing_word_hex: String,
|
|
pub count: usize,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpPeriodicCompanyServiceTraceReport {
|
|
pub profile_family: String,
|
|
#[serde(default)]
|
|
pub selected_company_id: Option<u32>,
|
|
#[serde(default)]
|
|
pub world_issue_37_present: bool,
|
|
#[serde(default)]
|
|
pub world_finance_neighborhood_present: bool,
|
|
#[serde(default)]
|
|
pub region_record_body_present: bool,
|
|
#[serde(default)]
|
|
pub placed_structure_record_body_present: bool,
|
|
#[serde(default)]
|
|
pub infrastructure_asset_side_buffer_present: bool,
|
|
pub peer_site_selector_candidate_owner_strip: String,
|
|
pub peer_site_selector_candidate_persisted_tag_hex: String,
|
|
pub peer_site_selector_candidate_selector_lane: String,
|
|
pub peer_site_selector_candidate_secondary_payload_lane: String,
|
|
pub peer_site_selector_candidate_post_secondary_byte_status: String,
|
|
pub peer_site_selector_candidate_class_identity_status: String,
|
|
#[serde(default)]
|
|
pub peer_site_selector_candidate_helper_linkage: Vec<String>,
|
|
#[serde(default)]
|
|
pub peer_site_selector_candidate_saved_payload_summaries:
|
|
Vec<SmpSavePlacedStructureProfilePayloadSummaryEntry>,
|
|
#[serde(default)]
|
|
pub peer_site_selector_candidate_saved_payload_delta_summaries:
|
|
Vec<SmpSavePlacedStructureProfilePayloadDeltaSummaryEntry>,
|
|
#[serde(default)]
|
|
pub peer_site_selector_candidate_saved_footer_padding_summaries:
|
|
Vec<SmpSavePlacedStructureProfileFooterPaddingSummaryEntry>,
|
|
#[serde(default)]
|
|
pub peer_site_selector_candidate_saved_companion_byte_summaries:
|
|
Vec<SmpSavePlacedStructureProfileCompanionByteSummaryEntry>,
|
|
#[serde(default)]
|
|
pub peer_site_selector_candidate_saved_policy_trailing_word_summaries:
|
|
Vec<SmpSavePlacedStructurePolicyTrailingWordSummaryEntry>,
|
|
#[serde(default)]
|
|
pub peer_site_selector_candidate_saved_nonzero_companion_name_pair_summaries:
|
|
Vec<SmpSavePlacedStructureNonzeroCompanionNamePairSummaryEntry>,
|
|
#[serde(default)]
|
|
pub peer_site_persisted_selector_bundle_fields: Vec<String>,
|
|
#[serde(default)]
|
|
pub peer_site_rebuilt_transient_followon_fields: Vec<String>,
|
|
pub peer_site_shellless_minimum_persisted_identity_status: String,
|
|
#[serde(default)]
|
|
pub peer_site_shellless_minimum_persisted_identity_inputs: Vec<String>,
|
|
#[serde(default)]
|
|
pub peer_site_restore_input_fields: Vec<String>,
|
|
#[serde(default)]
|
|
pub peer_site_runtime_input_fields: Vec<String>,
|
|
pub peer_site_runtime_reconstruction_status: String,
|
|
#[serde(default)]
|
|
pub peer_site_runtime_reconstruction_steps: Vec<String>,
|
|
#[serde(default)]
|
|
pub near_city_acquisition_region_input_fields: Vec<String>,
|
|
#[serde(default)]
|
|
pub near_city_acquisition_peer_input_fields: Vec<String>,
|
|
#[serde(default)]
|
|
pub near_city_acquisition_company_input_fields: Vec<String>,
|
|
pub near_city_acquisition_shellless_readiness_status: String,
|
|
#[serde(default)]
|
|
pub near_city_acquisition_runtime_backed_input_families: Vec<String>,
|
|
pub near_city_acquisition_site_owner_company_projection_status: String,
|
|
pub near_city_acquisition_site_self_id_projection_status: String,
|
|
pub near_city_acquisition_site_cached_tri_lane_projection_status: String,
|
|
pub near_city_acquisition_tri_lane_live_service_status: String,
|
|
pub near_city_acquisition_candidate_subtype_projection_status: String,
|
|
pub near_city_acquisition_backing_record_projection_status: String,
|
|
pub near_city_acquisition_nontransport_persisted_source_status: String,
|
|
#[serde(default)]
|
|
pub near_city_acquisition_nontransport_persisted_source_candidates: Vec<String>,
|
|
pub near_city_acquisition_tri_lane_save_shape_family_status: String,
|
|
#[serde(default)]
|
|
pub near_city_acquisition_tri_lane_save_shape_family_candidates:
|
|
Vec<SmpPeriodicTriLaneSaveShapeFamilyCandidateSummaryEntry>,
|
|
#[serde(default)]
|
|
pub near_city_acquisition_tri_lane_live_owner_families: Vec<String>,
|
|
#[serde(default)]
|
|
pub near_city_acquisition_tri_lane_candidate_gate_fields: Vec<String>,
|
|
#[serde(default)]
|
|
pub near_city_acquisition_tri_lane_runtime_writer_roles: Vec<String>,
|
|
#[serde(default)]
|
|
pub near_city_acquisition_tri_lane_direct_caller_families: Vec<String>,
|
|
#[serde(default)]
|
|
pub near_city_acquisition_tri_lane_formula_input_lanes: Vec<String>,
|
|
#[serde(default)]
|
|
pub near_city_acquisition_projection_hypotheses: Vec<SmpServiceConsumerHypothesis>,
|
|
#[serde(default)]
|
|
pub near_city_acquisition_remaining_owner_gaps: Vec<String>,
|
|
#[serde(default)]
|
|
pub near_city_acquisition_region_lane_statuses: Vec<String>,
|
|
#[serde(default)]
|
|
pub atlas_candidate_consumers: Vec<String>,
|
|
#[serde(default)]
|
|
pub known_bridge_helpers: Vec<String>,
|
|
#[serde(default)]
|
|
pub next_owner_questions: Vec<String>,
|
|
pub linked_transit_shellless_readiness_status: String,
|
|
#[serde(default)]
|
|
pub linked_transit_minimum_persisted_identity_inputs: Vec<String>,
|
|
#[serde(default)]
|
|
pub linked_transit_live_rebuilt_cache_lanes: Vec<String>,
|
|
#[serde(default)]
|
|
pub linked_transit_runtime_backed_input_families: Vec<String>,
|
|
#[serde(default)]
|
|
pub linked_transit_remaining_owner_gaps: Vec<String>,
|
|
#[serde(default)]
|
|
pub companies: Vec<SmpPeriodicCompanyServiceTraceEntry>,
|
|
#[serde(default)]
|
|
pub notes: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpPeriodicTriLaneSaveShapeFamilyCandidateSummaryEntry {
|
|
pub rank: usize,
|
|
pub shape_family_signature: String,
|
|
pub rows_offset_hex: String,
|
|
pub row_count: usize,
|
|
pub row_stride_hex: String,
|
|
#[serde(default)]
|
|
pub best_probable_density_lane_relative_offset_hex: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpRegionServiceTraceEntry {
|
|
pub name: String,
|
|
#[serde(default)]
|
|
pub profile_collection_count: Option<u32>,
|
|
pub policy_leading_f32_0_text: String,
|
|
pub policy_leading_f32_1_text: String,
|
|
pub policy_leading_f32_2_text: String,
|
|
#[serde(default)]
|
|
pub policy_reserved_dword_hex_words: Vec<String>,
|
|
pub policy_trailing_word_hex: String,
|
|
#[serde(default)]
|
|
pub branches: Vec<SmpServiceTraceBranchStatus>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpRegionServiceTraceReport {
|
|
pub profile_family: String,
|
|
#[serde(default)]
|
|
pub region_collection_header_present: bool,
|
|
#[serde(default)]
|
|
pub region_record_triplet_count: usize,
|
|
#[serde(default)]
|
|
pub queued_notice_record_count: usize,
|
|
#[serde(default)]
|
|
pub atlas_candidate_consumers: Vec<String>,
|
|
#[serde(default)]
|
|
pub known_owner_bridge_fields: Vec<String>,
|
|
#[serde(default)]
|
|
pub known_bridge_helpers: Vec<String>,
|
|
#[serde(default)]
|
|
pub next_owner_questions: Vec<String>,
|
|
#[serde(default)]
|
|
pub candidate_consumer_hypotheses: Vec<SmpServiceConsumerHypothesis>,
|
|
#[serde(default)]
|
|
pub entries: Vec<SmpRegionServiceTraceEntry>,
|
|
#[serde(default)]
|
|
pub notes: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpInfrastructureAssetTraceReport {
|
|
pub profile_family: String,
|
|
#[serde(default)]
|
|
pub placed_structure_collection_header_present: bool,
|
|
#[serde(default)]
|
|
pub placed_structure_record_triplet_count: usize,
|
|
#[serde(default)]
|
|
pub side_buffer_present: bool,
|
|
#[serde(default)]
|
|
pub side_buffer_decoded_embedded_name_row_count: usize,
|
|
#[serde(default)]
|
|
pub side_buffer_unique_name_pair_count: usize,
|
|
#[serde(default)]
|
|
pub bridge_like_name_pair_count: usize,
|
|
#[serde(default)]
|
|
pub tunnel_like_name_pair_count: usize,
|
|
#[serde(default)]
|
|
pub track_cap_like_name_pair_count: usize,
|
|
#[serde(default)]
|
|
pub triplet_alignment_overlap_count: usize,
|
|
#[serde(default)]
|
|
pub atlas_candidate_consumers: Vec<String>,
|
|
#[serde(default)]
|
|
pub known_owner_bridge_fields: Vec<String>,
|
|
#[serde(default)]
|
|
pub known_bridge_helpers: Vec<String>,
|
|
#[serde(default)]
|
|
pub next_owner_questions: Vec<String>,
|
|
#[serde(default)]
|
|
pub candidate_consumer_hypotheses: Vec<SmpServiceConsumerHypothesis>,
|
|
#[serde(default)]
|
|
pub branches: Vec<SmpServiceTraceBranchStatus>,
|
|
#[serde(default)]
|
|
pub notes: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpServiceConsumerHypothesis {
|
|
pub label: String,
|
|
pub status: String,
|
|
#[serde(default)]
|
|
pub candidate_consumers: Vec<String>,
|
|
#[serde(default)]
|
|
pub evidence: Vec<String>,
|
|
#[serde(default)]
|
|
pub blockers: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedSpecialConditionsTable {
|
|
pub source_kind: String,
|
|
pub table_offset: usize,
|
|
pub table_len: usize,
|
|
pub enabled_visible_count: usize,
|
|
pub enabled_visible_labels: Vec<String>,
|
|
pub entries: Vec<SmpSpecialConditionEntry>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedEventRuntimeCollectionSummary {
|
|
pub source_kind: String,
|
|
pub mechanism_family: String,
|
|
pub mechanism_confidence: String,
|
|
#[serde(default)]
|
|
pub container_profile_family: Option<String>,
|
|
pub metadata_tag_offset: usize,
|
|
pub records_tag_offset: usize,
|
|
pub close_tag_offset: usize,
|
|
pub packed_state_version: u32,
|
|
pub packed_state_version_hex: String,
|
|
pub live_id_bound: u32,
|
|
pub live_record_count: usize,
|
|
pub live_entry_ids: Vec<u32>,
|
|
#[serde(default)]
|
|
pub decoded_record_count: usize,
|
|
#[serde(default)]
|
|
pub imported_runtime_record_count: usize,
|
|
#[serde(default)]
|
|
pub records_with_trigger_kind: usize,
|
|
#[serde(default)]
|
|
pub records_missing_trigger_kind: usize,
|
|
#[serde(default)]
|
|
pub nondirect_compact_record_count: usize,
|
|
#[serde(default)]
|
|
pub nondirect_compact_records_missing_trigger_kind: usize,
|
|
#[serde(default)]
|
|
pub trigger_kinds_present: Vec<u8>,
|
|
#[serde(default)]
|
|
pub add_building_dispatch_strip_record_indexes: Vec<usize>,
|
|
#[serde(default)]
|
|
pub add_building_dispatch_strip_descriptor_labels: Vec<String>,
|
|
#[serde(default)]
|
|
pub add_building_dispatch_strip_records_with_trigger_kind: usize,
|
|
#[serde(default)]
|
|
pub add_building_dispatch_strip_records_missing_trigger_kind: usize,
|
|
#[serde(default)]
|
|
pub add_building_dispatch_strip_row_shape_families: Vec<String>,
|
|
#[serde(default)]
|
|
pub add_building_dispatch_strip_signature_families: Vec<String>,
|
|
#[serde(default)]
|
|
pub add_building_dispatch_strip_condition_tuple_families: Vec<String>,
|
|
#[serde(default)]
|
|
pub add_building_dispatch_strip_signature_condition_clusters: Vec<String>,
|
|
#[serde(default)]
|
|
pub control_lane_notes: Vec<String>,
|
|
#[serde(default)]
|
|
pub records: Vec<SmpLoadedPackedEventRecordSummary>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedPackedEventRecordSummary {
|
|
pub record_index: usize,
|
|
pub live_entry_id: u32,
|
|
#[serde(default)]
|
|
pub payload_offset: Option<usize>,
|
|
#[serde(default)]
|
|
pub payload_len: Option<usize>,
|
|
pub decode_status: String,
|
|
#[serde(default)]
|
|
pub payload_family: String,
|
|
#[serde(default)]
|
|
pub trigger_kind: Option<u8>,
|
|
#[serde(default)]
|
|
pub active: Option<bool>,
|
|
#[serde(default)]
|
|
pub marks_collection_dirty: Option<bool>,
|
|
#[serde(default)]
|
|
pub one_shot: Option<bool>,
|
|
#[serde(default)]
|
|
pub compact_control: Option<SmpLoadedPackedEventCompactControlSummary>,
|
|
#[serde(default)]
|
|
pub text_bands: Vec<SmpLoadedPackedEventTextBandSummary>,
|
|
#[serde(default)]
|
|
pub standalone_condition_row_count: usize,
|
|
#[serde(default)]
|
|
pub standalone_condition_rows: Vec<SmpLoadedPackedEventConditionRowSummary>,
|
|
#[serde(default)]
|
|
pub negative_sentinel_scope: Option<SmpLoadedPackedEventNegativeSentinelScopeSummary>,
|
|
#[serde(default)]
|
|
pub grouped_effect_row_counts: Vec<usize>,
|
|
#[serde(default)]
|
|
pub grouped_effect_rows: Vec<SmpLoadedPackedEventGroupedEffectRowSummary>,
|
|
#[serde(default)]
|
|
pub decoded_conditions: Vec<RuntimeCondition>,
|
|
#[serde(default)]
|
|
pub decoded_actions: Vec<RuntimeEffect>,
|
|
#[serde(default)]
|
|
pub executable_import_ready: bool,
|
|
#[serde(default)]
|
|
pub notes: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedPackedEventNegativeSentinelScopeSummary {
|
|
pub company_test_scope: RuntimeCompanyConditionTestScope,
|
|
pub player_test_scope: RuntimePlayerConditionTestScope,
|
|
pub territory_scope_selector_is_0x63: bool,
|
|
#[serde(default)]
|
|
pub source_row_indexes: Vec<usize>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedPackedEventCompactControlSummary {
|
|
pub mode_byte_0x7ef: u8,
|
|
pub primary_selector_0x7f0: u32,
|
|
pub grouped_mode_0x7f4: u8,
|
|
pub one_shot_header_0x7f5: u32,
|
|
pub modifier_flag_0x7f9: u8,
|
|
pub modifier_flag_0x7fa: u8,
|
|
pub grouped_target_scope_ordinals_0x7fb: Vec<u8>,
|
|
pub grouped_scope_checkboxes_0x7ff: Vec<u8>,
|
|
pub summary_toggle_0x800: u8,
|
|
pub grouped_territory_selectors_0x80f: Vec<i32>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedPackedEventTextBandSummary {
|
|
pub label: String,
|
|
pub packed_len: usize,
|
|
pub present: bool,
|
|
pub preview: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedPackedEventConditionRowSummary {
|
|
pub row_index: usize,
|
|
pub raw_condition_id: i32,
|
|
pub subtype: u8,
|
|
#[serde(default)]
|
|
pub flag_bytes: Vec<u8>,
|
|
#[serde(default)]
|
|
pub candidate_name: Option<String>,
|
|
#[serde(default)]
|
|
pub comparator: Option<String>,
|
|
#[serde(default)]
|
|
pub metric: Option<String>,
|
|
#[serde(default)]
|
|
pub semantic_family: Option<String>,
|
|
#[serde(default)]
|
|
pub semantic_preview: Option<String>,
|
|
#[serde(default)]
|
|
pub recovered_cargo_slot: Option<u32>,
|
|
#[serde(default)]
|
|
pub recovered_cargo_class: Option<String>,
|
|
#[serde(default)]
|
|
pub requires_candidate_name_binding: bool,
|
|
#[serde(default)]
|
|
pub notes: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedPackedEventGroupedEffectRowSummary {
|
|
pub group_index: usize,
|
|
pub row_index: usize,
|
|
pub descriptor_id: u32,
|
|
#[serde(default)]
|
|
pub descriptor_label: Option<String>,
|
|
#[serde(default)]
|
|
pub target_mask_bits: Option<u8>,
|
|
#[serde(default)]
|
|
pub parameter_family: Option<String>,
|
|
#[serde(default)]
|
|
pub grouped_target_subject: Option<String>,
|
|
#[serde(default)]
|
|
pub grouped_target_scope: Option<String>,
|
|
pub opcode: u8,
|
|
pub raw_scalar_value: i32,
|
|
pub value_byte_0x09: u8,
|
|
pub value_dword_0x0d: u32,
|
|
pub value_byte_0x11: u8,
|
|
pub value_byte_0x12: u8,
|
|
pub value_word_0x14: u16,
|
|
pub value_word_0x16: u16,
|
|
pub row_shape: String,
|
|
#[serde(default)]
|
|
pub semantic_family: Option<String>,
|
|
#[serde(default)]
|
|
pub semantic_preview: Option<String>,
|
|
#[serde(default)]
|
|
pub recovered_cargo_slot: Option<u32>,
|
|
#[serde(default)]
|
|
pub recovered_cargo_class: Option<String>,
|
|
#[serde(default)]
|
|
pub recovered_cargo_label: Option<String>,
|
|
#[serde(default)]
|
|
pub recovered_locomotive_id: Option<u32>,
|
|
#[serde(default)]
|
|
pub locomotive_name: Option<String>,
|
|
#[serde(default)]
|
|
pub notes: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
enum RealGroupedTargetSubject {
|
|
Company,
|
|
Player,
|
|
Chairman,
|
|
Territory,
|
|
WholeGame,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpLoadedSaveSlice {
|
|
pub file_extension_hint: Option<String>,
|
|
pub container_profile_family: Option<String>,
|
|
pub mechanism_family: String,
|
|
pub mechanism_confidence: String,
|
|
pub trailer_family: Option<String>,
|
|
pub bridge_family: Option<String>,
|
|
pub profile: Option<SmpLoadedProfile>,
|
|
pub candidate_availability_table: Option<SmpLoadedCandidateAvailabilityTable>,
|
|
pub named_locomotive_availability_table: Option<SmpLoadedNamedLocomotiveAvailabilityTable>,
|
|
#[serde(default)]
|
|
pub locomotive_catalog: Option<SmpLoadedLocomotiveCatalog>,
|
|
#[serde(default)]
|
|
pub cargo_catalog: Option<SmpLoadedCargoCatalog>,
|
|
#[serde(default)]
|
|
pub world_issue_37_state: Option<SmpLoadedWorldIssue37State>,
|
|
#[serde(default)]
|
|
pub world_economic_tuning_state: Option<SmpLoadedWorldEconomicTuningState>,
|
|
#[serde(default)]
|
|
pub world_finance_neighborhood_state: Option<SmpLoadedWorldFinanceNeighborhoodState>,
|
|
#[serde(default)]
|
|
pub world_locomotive_policy_state: Option<SmpLoadedWorldLocomotivePolicyState>,
|
|
#[serde(default)]
|
|
pub company_roster: Option<SmpLoadedCompanyRoster>,
|
|
#[serde(default)]
|
|
pub chairman_profile_table: Option<SmpLoadedChairmanProfileTable>,
|
|
#[serde(default)]
|
|
pub region_collection: Option<SmpLoadedRegionCollection>,
|
|
#[serde(default)]
|
|
pub region_fixed_row_run_summary: Option<SmpLoadedRegionFixedRowRunSummary>,
|
|
#[serde(default)]
|
|
pub placed_structure_collection: Option<SmpLoadedPlacedStructureCollection>,
|
|
#[serde(default)]
|
|
pub placed_structure_dynamic_side_buffer_summary:
|
|
Option<SmpLoadedPlacedStructureDynamicSideBufferSummary>,
|
|
pub special_conditions_table: Option<SmpLoadedSpecialConditionsTable>,
|
|
pub event_runtime_collection: Option<SmpLoadedEventRuntimeCollectionSummary>,
|
|
pub notes: Vec<String>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct SmpPackedProfileWordLane {
|
|
pub relative_offset: usize,
|
|
pub relative_offset_hex: String,
|
|
pub value: u32,
|
|
pub value_hex: String,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SmpInspectionReport {
|
|
pub inspection_mode: String,
|
|
pub file_extension_hint: Option<String>,
|
|
pub file_size: usize,
|
|
pub sha256: String,
|
|
pub preamble: SmpPreamble,
|
|
pub shared_header: Option<SmpSharedHeader>,
|
|
pub header_variant_probe: Option<SmpHeaderVariantProbe>,
|
|
pub first_ascii_run: Option<SmpAsciiPreview>,
|
|
pub early_content_probe: Option<SmpEarlyContentProbe>,
|
|
pub secondary_variant_probe: Option<SmpSecondaryVariantProbe>,
|
|
pub container_profile: Option<SmpContainerProfile>,
|
|
pub save_bootstrap_block: Option<SmpSaveBootstrapBlock>,
|
|
pub save_anchor_run_block: Option<SmpSaveAnchorRunBlock>,
|
|
pub runtime_anchor_cycle_block: Option<SmpRuntimeAnchorCycleBlock>,
|
|
pub runtime_trailer_block: Option<SmpRuntimeTrailerBlock>,
|
|
pub runtime_post_span_probe: Option<SmpRuntimePostSpanProbe>,
|
|
pub rt3_105_post_span_bridge_probe: Option<SmpRt3105PostSpanBridgeProbe>,
|
|
pub rt3_105_save_bridge_payload_probe: Option<SmpRt3105SaveBridgePayloadProbe>,
|
|
pub save_world_selection_context_probe: Option<SmpSaveWorldSelectionContextProbe>,
|
|
pub save_world_issue_37_probe: Option<SmpSaveWorldIssue37Probe>,
|
|
pub save_world_economic_tuning_probe: Option<SmpSaveWorldEconomicTuningProbe>,
|
|
pub save_world_finance_neighborhood_probe: Option<SmpSaveWorldFinanceNeighborhoodProbe>,
|
|
pub save_company_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
|
pub save_chairman_profile_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
|
pub save_train_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
|
pub save_train_collection_directory_probe: Option<SmpSaveTrainCollectionDirectoryProbe>,
|
|
pub save_region_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
|
pub save_region_record_triplet_probe: Option<SmpSaveRegionRecordTripletProbe>,
|
|
#[serde(default)]
|
|
pub save_region_queued_notice_record_probe: Option<SmpSaveRegionQueuedNoticeRecordProbe>,
|
|
#[serde(default)]
|
|
pub save_region_fixed_row_run_candidate_probe: Option<SmpSaveRegionFixedRowRunCandidateProbe>,
|
|
pub save_placed_structure_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
|
pub save_placed_structure_record_triplet_probe:
|
|
Option<SmpSavePlacedStructureRecordTripletProbe>,
|
|
#[serde(default)]
|
|
pub save_placed_structure_dynamic_side_buffer_probe:
|
|
Option<SmpSavePlacedStructureDynamicSideBufferProbe>,
|
|
#[serde(default)]
|
|
pub save_unclassified_tagged_collection_header_probes:
|
|
Vec<SmpSaveUnclassifiedTaggedCollectionHeaderProbe>,
|
|
#[serde(default)]
|
|
pub save_company_roster_probe: Option<SmpLoadedCompanyRoster>,
|
|
#[serde(default)]
|
|
pub save_chairman_profile_table_probe: Option<SmpLoadedChairmanProfileTable>,
|
|
pub rt3_105_save_name_table_probe: Option<SmpRt3105SaveNameTableProbe>,
|
|
pub rt3_105_save_named_locomotive_availability_probe:
|
|
Option<SmpRt3105SaveNamedLocomotiveAvailabilityProbe>,
|
|
pub special_conditions_probe: Option<SmpSpecialConditionsProbe>,
|
|
pub smp_aligned_runtime_rule_band_probe: Option<SmpAlignedRuntimeRuleBandProbe>,
|
|
pub post_special_conditions_scalar_probe: Option<SmpPostSpecialConditionsScalarProbe>,
|
|
pub post_text_field_neighborhood_probe: Option<SmpPostTextFieldNeighborhoodProbe>,
|
|
pub locomotive_policy_neighborhood_probe: Option<SmpLocomotivePolicyNeighborhoodProbe>,
|
|
pub pre_recipe_scalar_plateau_probe: Option<SmpPreRecipeScalarPlateauProbe>,
|
|
pub recipe_book_summary_probe: Option<SmpRecipeBookSummaryProbe>,
|
|
pub classic_rehydrate_profile_probe: Option<SmpClassicRehydrateProfileProbe>,
|
|
pub rt3_105_packed_profile_probe: Option<SmpRt3105PackedProfileProbe>,
|
|
pub save_load_summary: Option<SmpSaveLoadSummary>,
|
|
pub event_runtime_collection_summary: Option<SmpLoadedEventRuntimeCollectionSummary>,
|
|
pub contains_grounded_runtime_tags: bool,
|
|
pub known_tag_hits: Vec<SmpKnownTagHit>,
|
|
pub notes: Vec<String>,
|
|
pub warnings: Vec<String>,
|
|
}
|
|
|
|
pub fn inspect_smp_file(path: &Path) -> Result<SmpInspectionReport, Box<dyn std::error::Error>> {
|
|
let bytes = fs::read(path)?;
|
|
Ok(inspect_bundle_bytes(
|
|
&bytes,
|
|
path.extension()
|
|
.and_then(|extension| extension.to_str())
|
|
.map(|extension| extension.to_ascii_lowercase()),
|
|
))
|
|
}
|
|
|
|
pub fn inspect_unclassified_save_collection_headers_file(
|
|
path: &Path,
|
|
) -> Result<Vec<SmpSaveUnclassifiedTaggedCollectionHeaderProbe>, Box<dyn std::error::Error>> {
|
|
let bytes = fs::read(path)?;
|
|
let file_extension_hint = path
|
|
.extension()
|
|
.and_then(|extension| extension.to_str())
|
|
.map(|extension| extension.to_ascii_lowercase());
|
|
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 save_company_collection_header_probe = parse_save_company_collection_header_probe(
|
|
&bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let save_chairman_profile_collection_header_probe =
|
|
parse_save_chairman_profile_collection_header_probe(
|
|
&bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let save_train_collection_header_probe = parse_save_train_collection_header_probe(
|
|
&bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let save_region_collection_header_probe = parse_save_region_collection_header_probe(
|
|
&bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let save_placed_structure_collection_header_probe =
|
|
parse_save_placed_structure_collection_header_probe(
|
|
&bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let known_header_probes = [
|
|
save_company_collection_header_probe.as_ref(),
|
|
save_chairman_profile_collection_header_probe.as_ref(),
|
|
save_train_collection_header_probe.as_ref(),
|
|
save_region_collection_header_probe.as_ref(),
|
|
save_placed_structure_collection_header_probe.as_ref(),
|
|
];
|
|
let probes = scan_save_unclassified_tagged_collection_header_probes(
|
|
&bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
Ok(
|
|
filter_unclassified_tagged_collection_header_probes_outside_known_spans(
|
|
probes,
|
|
&known_header_probes,
|
|
),
|
|
)
|
|
}
|
|
|
|
pub fn inspect_save_placed_structure_dynamic_side_buffer_file(
|
|
path: &Path,
|
|
) -> Result<Option<SmpSavePlacedStructureDynamicSideBufferProbe>, Box<dyn std::error::Error>> {
|
|
let bytes = fs::read(path)?;
|
|
let file_extension_hint = path
|
|
.extension()
|
|
.and_then(|extension| extension.to_str())
|
|
.map(|extension| extension.to_ascii_lowercase());
|
|
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(),
|
|
);
|
|
Ok(parse_save_placed_structure_dynamic_side_buffer_probe(
|
|
&bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
))
|
|
}
|
|
|
|
pub fn inspect_save_region_queued_notice_records_file(
|
|
path: &Path,
|
|
) -> Result<Option<SmpSaveRegionQueuedNoticeRecordProbe>, Box<dyn std::error::Error>> {
|
|
let bytes = fs::read(path)?;
|
|
let file_extension_hint = path
|
|
.extension()
|
|
.and_then(|extension| extension.to_str())
|
|
.map(|extension| extension.to_ascii_lowercase());
|
|
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 save_region_collection_header_probe = parse_save_region_collection_header_probe(
|
|
&bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
Ok(parse_save_region_queued_notice_record_probe(
|
|
&bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
save_region_collection_header_probe.as_ref(),
|
|
))
|
|
}
|
|
|
|
fn build_service_trace_branch_status(
|
|
branch_name: &str,
|
|
status: &str,
|
|
grounded_inputs: &[&str],
|
|
blocking_inputs: &[&str],
|
|
candidate_consumers: &[&str],
|
|
notes: &[&str],
|
|
) -> SmpServiceTraceBranchStatus {
|
|
SmpServiceTraceBranchStatus {
|
|
branch_name: branch_name.to_string(),
|
|
status: status.to_string(),
|
|
grounded_inputs: grounded_inputs
|
|
.iter()
|
|
.map(|value| value.to_string())
|
|
.collect(),
|
|
blocking_inputs: blocking_inputs
|
|
.iter()
|
|
.map(|value| value.to_string())
|
|
.collect(),
|
|
candidate_consumers: candidate_consumers
|
|
.iter()
|
|
.map(|value| value.to_string())
|
|
.collect(),
|
|
notes: notes.iter().map(|value| value.to_string()).collect(),
|
|
}
|
|
}
|
|
|
|
pub fn inspect_save_periodic_company_service_trace_file(
|
|
path: &Path,
|
|
) -> Result<SmpPeriodicCompanyServiceTraceReport, Box<dyn std::error::Error>> {
|
|
let inspection = inspect_smp_file(path)?;
|
|
let analysis = inspect_save_company_and_chairman_analysis_file(path)?;
|
|
let mut trace = build_periodic_company_service_trace_report(&analysis);
|
|
let _ = inspection;
|
|
if trace.region_record_body_present || trace.placed_structure_record_body_present {
|
|
trace.notes.push(
|
|
"The current blockers are no longer collection identity; they are missing higher-layer consumer semantics for the region and infrastructure/placed-structure owner seams.".to_string(),
|
|
);
|
|
}
|
|
Ok(trace)
|
|
}
|
|
|
|
fn summarize_peer_site_selector_candidate_saved_payloads(
|
|
analysis: &SmpSaveCompanyChairmanAnalysisReport,
|
|
) -> Vec<SmpSavePlacedStructureProfilePayloadSummaryEntry> {
|
|
let Some(triplets) = analysis.placed_structure_record_triplets.as_ref() else {
|
|
return Vec::new();
|
|
};
|
|
let mut grouped =
|
|
BTreeMap::<(String, String), (usize, BTreeSet<String>, BTreeSet<String>)>::new();
|
|
for entry in &triplets.entries {
|
|
let grouped_entry = grouped
|
|
.entry((
|
|
entry.profile_payload_dword_hex.clone(),
|
|
entry.profile_status_kind.clone(),
|
|
))
|
|
.or_insert_with(|| (0, BTreeSet::new(), BTreeSet::new()));
|
|
grouped_entry.0 += 1;
|
|
if grouped_entry.1.len() < 4 {
|
|
grouped_entry.1.insert(entry.primary_name.clone());
|
|
}
|
|
if grouped_entry.2.len() < 4 {
|
|
grouped_entry.2.insert(entry.secondary_name.clone());
|
|
}
|
|
}
|
|
let mut summaries = grouped
|
|
.into_iter()
|
|
.map(
|
|
|(
|
|
(profile_payload_dword_hex, profile_status_kind),
|
|
(count, primary_names, secondary_names),
|
|
)| {
|
|
SmpSavePlacedStructureProfilePayloadSummaryEntry {
|
|
profile_payload_dword_hex,
|
|
profile_status_kind,
|
|
count,
|
|
sample_primary_names: primary_names.into_iter().collect(),
|
|
sample_secondary_names: secondary_names.into_iter().collect(),
|
|
}
|
|
},
|
|
)
|
|
.collect::<Vec<_>>();
|
|
summaries.sort_by(|left, right| {
|
|
right
|
|
.count
|
|
.cmp(&left.count)
|
|
.then_with(|| {
|
|
left.profile_payload_dword_hex
|
|
.cmp(&right.profile_payload_dword_hex)
|
|
})
|
|
.then_with(|| left.profile_status_kind.cmp(&right.profile_status_kind))
|
|
});
|
|
summaries.truncate(8);
|
|
summaries
|
|
}
|
|
|
|
fn summarize_peer_site_selector_candidate_saved_payload_deltas(
|
|
analysis: &SmpSaveCompanyChairmanAnalysisReport,
|
|
) -> Vec<SmpSavePlacedStructureProfilePayloadDeltaSummaryEntry> {
|
|
let Some(triplets) = analysis.placed_structure_record_triplets.as_ref() else {
|
|
return Vec::new();
|
|
};
|
|
let mut payload_values = triplets
|
|
.entries
|
|
.iter()
|
|
.map(|entry| entry.profile_payload_dword)
|
|
.collect::<Vec<_>>();
|
|
payload_values.sort_unstable();
|
|
payload_values.dedup();
|
|
let mut delta_counts = BTreeMap::<u32, usize>::new();
|
|
for window in payload_values.windows(2) {
|
|
let delta = window[1].wrapping_sub(window[0]);
|
|
*delta_counts.entry(delta).or_insert(0) += 1;
|
|
}
|
|
let mut summaries = delta_counts
|
|
.into_iter()
|
|
.map(
|
|
|(delta, count)| SmpSavePlacedStructureProfilePayloadDeltaSummaryEntry {
|
|
delta_hex: format!("0x{delta:08x}"),
|
|
count,
|
|
},
|
|
)
|
|
.collect::<Vec<_>>();
|
|
summaries.sort_by(|left, right| {
|
|
right
|
|
.count
|
|
.cmp(&left.count)
|
|
.then_with(|| left.delta_hex.cmp(&right.delta_hex))
|
|
});
|
|
summaries.truncate(6);
|
|
summaries
|
|
}
|
|
|
|
fn summarize_peer_site_selector_candidate_saved_footer_padding(
|
|
analysis: &SmpSaveCompanyChairmanAnalysisReport,
|
|
) -> Vec<SmpSavePlacedStructureProfileFooterPaddingSummaryEntry> {
|
|
let Some(triplets) = analysis.placed_structure_record_triplets.as_ref() else {
|
|
return Vec::new();
|
|
};
|
|
let mut grouped = BTreeMap::<usize, (usize, BTreeSet<String>)>::new();
|
|
for entry in &triplets.entries {
|
|
let grouped_entry = grouped
|
|
.entry(entry.profile_pre_footer_padding_len)
|
|
.or_insert_with(|| (0, BTreeSet::new()));
|
|
grouped_entry.0 += 1;
|
|
if grouped_entry.1.len() < 4 {
|
|
grouped_entry
|
|
.1
|
|
.insert(entry.profile_pre_footer_padding_hex_bytes.join(","));
|
|
}
|
|
}
|
|
let mut summaries = grouped
|
|
.into_iter()
|
|
.map(|(padding_len, (count, sample_hex_bytes))| {
|
|
SmpSavePlacedStructureProfileFooterPaddingSummaryEntry {
|
|
padding_len,
|
|
count,
|
|
sample_hex_bytes: sample_hex_bytes.into_iter().collect(),
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
summaries.sort_by(|left, right| {
|
|
right
|
|
.count
|
|
.cmp(&left.count)
|
|
.then_with(|| left.padding_len.cmp(&right.padding_len))
|
|
});
|
|
summaries.truncate(6);
|
|
summaries
|
|
}
|
|
|
|
fn summarize_peer_site_selector_candidate_saved_companion_bytes(
|
|
analysis: &SmpSaveCompanyChairmanAnalysisReport,
|
|
) -> Vec<SmpSavePlacedStructureProfileCompanionByteSummaryEntry> {
|
|
let Some(triplets) = analysis.placed_structure_record_triplets.as_ref() else {
|
|
return Vec::new();
|
|
};
|
|
let mut grouped = BTreeMap::<String, (usize, BTreeSet<String>, BTreeSet<String>)>::new();
|
|
for entry in &triplets.entries {
|
|
let Some(companion_byte_hex) = entry.profile_companion_byte_hex.as_ref() else {
|
|
continue;
|
|
};
|
|
let grouped_entry = grouped
|
|
.entry(companion_byte_hex.clone())
|
|
.or_insert_with(|| (0, BTreeSet::new(), BTreeSet::new()));
|
|
grouped_entry.0 += 1;
|
|
if grouped_entry.1.len() < 4 {
|
|
grouped_entry.1.insert(entry.primary_name.clone());
|
|
}
|
|
if grouped_entry.2.len() < 4 {
|
|
grouped_entry.2.insert(entry.secondary_name.clone());
|
|
}
|
|
}
|
|
let mut summaries = grouped
|
|
.into_iter()
|
|
.map(
|
|
|(companion_byte_hex, (count, primary_names, secondary_names))| {
|
|
SmpSavePlacedStructureProfileCompanionByteSummaryEntry {
|
|
companion_byte_hex,
|
|
count,
|
|
sample_primary_names: primary_names.into_iter().collect(),
|
|
sample_secondary_names: secondary_names.into_iter().collect(),
|
|
}
|
|
},
|
|
)
|
|
.collect::<Vec<_>>();
|
|
summaries.sort_by(|left, right| {
|
|
right
|
|
.count
|
|
.cmp(&left.count)
|
|
.then_with(|| left.companion_byte_hex.cmp(&right.companion_byte_hex))
|
|
});
|
|
summaries.truncate(8);
|
|
summaries
|
|
}
|
|
|
|
fn summarize_peer_site_selector_candidate_saved_policy_trailing_words(
|
|
analysis: &SmpSaveCompanyChairmanAnalysisReport,
|
|
) -> Vec<SmpSavePlacedStructurePolicyTrailingWordSummaryEntry> {
|
|
let Some(triplets) = analysis.placed_structure_record_triplets.as_ref() else {
|
|
return Vec::new();
|
|
};
|
|
let mut grouped = BTreeMap::<String, usize>::new();
|
|
for entry in &triplets.entries {
|
|
*grouped
|
|
.entry(entry.policy_trailing_word_hex.clone())
|
|
.or_insert(0) += 1;
|
|
}
|
|
let mut summaries = grouped
|
|
.into_iter()
|
|
.map(|(policy_trailing_word_hex, count)| {
|
|
SmpSavePlacedStructurePolicyTrailingWordSummaryEntry {
|
|
policy_trailing_word_hex,
|
|
count,
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
summaries.sort_by(|left, right| {
|
|
right.count.cmp(&left.count).then_with(|| {
|
|
left.policy_trailing_word_hex
|
|
.cmp(&right.policy_trailing_word_hex)
|
|
})
|
|
});
|
|
summaries.truncate(8);
|
|
summaries
|
|
}
|
|
|
|
fn summarize_peer_site_selector_candidate_saved_nonzero_companion_name_pairs(
|
|
analysis: &SmpSaveCompanyChairmanAnalysisReport,
|
|
) -> Vec<SmpSavePlacedStructureNonzeroCompanionNamePairSummaryEntry> {
|
|
let Some(triplets) = analysis.placed_structure_record_triplets.as_ref() else {
|
|
return Vec::new();
|
|
};
|
|
let mut grouped = BTreeMap::<(String, String, String), usize>::new();
|
|
for entry in &triplets.entries {
|
|
let Some(companion_byte_hex) = entry.profile_companion_byte_hex.as_ref() else {
|
|
continue;
|
|
};
|
|
if companion_byte_hex == "0x00" {
|
|
continue;
|
|
}
|
|
*grouped
|
|
.entry((
|
|
companion_byte_hex.clone(),
|
|
entry.primary_name.clone(),
|
|
entry.secondary_name.clone(),
|
|
))
|
|
.or_insert(0) += 1;
|
|
}
|
|
let mut summaries = grouped
|
|
.into_iter()
|
|
.map(
|
|
|((companion_byte_hex, primary_name, secondary_name), count)| {
|
|
SmpSavePlacedStructureNonzeroCompanionNamePairSummaryEntry {
|
|
companion_byte_hex,
|
|
primary_name,
|
|
secondary_name,
|
|
count,
|
|
}
|
|
},
|
|
)
|
|
.collect::<Vec<_>>();
|
|
summaries.sort_by(|left, right| {
|
|
right
|
|
.count
|
|
.cmp(&left.count)
|
|
.then_with(|| left.companion_byte_hex.cmp(&right.companion_byte_hex))
|
|
.then_with(|| left.primary_name.cmp(&right.primary_name))
|
|
.then_with(|| left.secondary_name.cmp(&right.secondary_name))
|
|
});
|
|
summaries.truncate(10);
|
|
summaries
|
|
}
|
|
|
|
fn summarize_near_city_acquisition_tri_lane_save_shape_family_candidates(
|
|
analysis: &SmpSaveCompanyChairmanAnalysisReport,
|
|
) -> Vec<SmpPeriodicTriLaneSaveShapeFamilyCandidateSummaryEntry> {
|
|
let Some(probe) = analysis.region_fixed_row_run_candidates.as_ref() else {
|
|
return Vec::new();
|
|
};
|
|
probe
|
|
.candidates
|
|
.iter()
|
|
.take(5)
|
|
.enumerate()
|
|
.map(
|
|
|(index, candidate)| SmpPeriodicTriLaneSaveShapeFamilyCandidateSummaryEntry {
|
|
rank: index + 1,
|
|
shape_family_signature: candidate.shape_family_signature.clone(),
|
|
rows_offset_hex: candidate.rows_offset_hex.clone(),
|
|
row_count: candidate.row_count,
|
|
row_stride_hex: candidate.row_stride_hex.clone(),
|
|
best_probable_density_lane_relative_offset_hex: candidate
|
|
.best_probable_density_lane_relative_offset_hex
|
|
.clone(),
|
|
},
|
|
)
|
|
.collect()
|
|
}
|
|
|
|
fn build_periodic_company_service_trace_report(
|
|
analysis: &SmpSaveCompanyChairmanAnalysisReport,
|
|
) -> SmpPeriodicCompanyServiceTraceReport {
|
|
let profile_family = analysis.profile_family.clone();
|
|
let selected_company_id = analysis.selected_company_id;
|
|
let region_record_body_present = analysis.region_record_triplets.is_some();
|
|
let placed_structure_record_body_present = analysis.placed_structure_record_triplets.is_some();
|
|
let infrastructure_asset_side_buffer_present =
|
|
analysis.placed_structure_dynamic_side_buffer.is_some();
|
|
let world_issue_37_present = analysis.world_issue_37.is_some();
|
|
let world_finance_neighborhood_present = analysis.world_finance_neighborhood.is_some();
|
|
let peer_site_selector_candidate_owner_strip =
|
|
"0x0045c150 -> 0x0045c310 -> 0x0045c36e -> 0x00456100 -> 0x00455b70".to_string();
|
|
let peer_site_selector_candidate_persisted_tag_hex = "0x5dc1".to_string();
|
|
let peer_site_selector_candidate_selector_lane = "[owner+0x23e]".to_string();
|
|
let peer_site_selector_candidate_secondary_payload_lane = "[owner+0x242]".to_string();
|
|
let peer_site_selector_candidate_post_secondary_byte_status =
|
|
"unresolved 0x5dc1 post-secondary discriminator byte after the repeated secondary payload string".to_string();
|
|
let peer_site_selector_candidate_class_identity_status =
|
|
"grounded_direct_local_helper_strip".to_string();
|
|
let peer_site_selector_candidate_helper_linkage = vec![
|
|
"0x0040ceab -> 0x0045c150".to_string(),
|
|
"0x0040d1a1 -> 0x0045c310".to_string(),
|
|
"0x0040cd70 seeds [site+0x3cc/+0x3d0] from 0x62b2fc / 0x62b268".to_string(),
|
|
"0x0040d1e1 -> 0x0045c3c0 consumes the same owner family's [site+0x246] child lane"
|
|
.to_string(),
|
|
];
|
|
let peer_site_selector_candidate_saved_payload_summaries =
|
|
summarize_peer_site_selector_candidate_saved_payloads(analysis);
|
|
let peer_site_selector_candidate_saved_payload_delta_summaries =
|
|
summarize_peer_site_selector_candidate_saved_payload_deltas(analysis);
|
|
let peer_site_selector_candidate_saved_footer_padding_summaries =
|
|
summarize_peer_site_selector_candidate_saved_footer_padding(analysis);
|
|
let peer_site_selector_candidate_saved_companion_byte_summaries =
|
|
summarize_peer_site_selector_candidate_saved_companion_bytes(analysis);
|
|
let peer_site_selector_candidate_saved_policy_trailing_word_summaries =
|
|
summarize_peer_site_selector_candidate_saved_policy_trailing_words(analysis);
|
|
let peer_site_selector_candidate_saved_nonzero_companion_name_pair_summaries =
|
|
summarize_peer_site_selector_candidate_saved_nonzero_companion_name_pairs(analysis);
|
|
let peer_site_persisted_selector_bundle_fields = vec![
|
|
"0x5dc1 payload lane [owner+0x23e] restored by 0x0045c150 and later fed into 0x0045c36e"
|
|
.to_string(),
|
|
"0x5dc1 payload lane [owner+0x242] restored by 0x0045c150 as the repeated secondary payload string"
|
|
.to_string(),
|
|
"0x5dc1 post-secondary one-byte residue after the repeated secondary payload string"
|
|
.to_string(),
|
|
"broader saved child/runtime selector bundle [owner+0x246/+0x24e/+0x252] emitted by 0x0040c980 -> 0x0045b560"
|
|
.to_string(),
|
|
];
|
|
let peer_site_rebuilt_transient_followon_fields = vec![
|
|
"[owner+0x246] primary transient handle rebuilt from payload strings by 0x0045c310"
|
|
.to_string(),
|
|
"[owner+0x24a] ambient transient rebuilt through 0x0045b210 after 0x0045b5f0 refreshes the current derived position scalar"
|
|
.to_string(),
|
|
"transient roots [owner+0x24e/+0x256/+0x25a/+0x25e] cleared by 0x0045c150 before 0x0045b5f0 / 0x0045b6f0 rebuild follow-on variant state"
|
|
.to_string(),
|
|
"larger animation/light/random-sound variant family rooted in [owner+0x23e] rebuilt through 0x0045b6f0 / 0x0045b760 / 0x0045baf0"
|
|
.to_string(),
|
|
];
|
|
let peer_site_shellless_minimum_persisted_identity_status =
|
|
"name_pair_and_post_secondary_byte_minimum_identity_subset_child_runtime_bundle_rebuild_followon".to_string();
|
|
let peer_site_shellless_minimum_persisted_identity_inputs = vec![
|
|
"[site+0x3cc] cached source placed-structure id".to_string(),
|
|
"[site+0x3d0] cached companion candidate/profile id".to_string(),
|
|
"0x5dc1 payload lane [owner+0x23e]".to_string(),
|
|
"0x5dc1 payload lane [owner+0x242]".to_string(),
|
|
"0x5dc1 post-secondary one-byte residue".to_string(),
|
|
];
|
|
let peer_site_restore_input_fields = vec![
|
|
"[site+0x3cc] saved placed-structure id feeding 0x62b2fc".to_string(),
|
|
"[site+0x3d0] saved companion-region id from [placed+0x173] feeding 0x62b268".to_string(),
|
|
"0x5dc1 payload lane [owner+0x23e] feeding 0x0045c36e selector arg 1".to_string(),
|
|
"0x5dc1 payload lane [owner+0x242] carrying the restored secondary payload string"
|
|
.to_string(),
|
|
"0x5dc1 post-secondary one-byte residue after the repeated secondary payload string"
|
|
.to_string(),
|
|
];
|
|
let peer_site_runtime_input_fields = vec![
|
|
"[site+0x04] live backing-record selector consumed by 0x0047efe0 / 0x0047fd50".to_string(),
|
|
"[site+0x2a8] linked peer-site id consumed by 0x0040d1f0".to_string(),
|
|
"[peer+0x08] route-entry anchor id consumed by 0x0047dda0".to_string(),
|
|
];
|
|
let peer_site_runtime_reconstruction_status =
|
|
"restore_subset_and_bring_up_reconstruct_runtime_subset".to_string();
|
|
let peer_site_runtime_reconstruction_steps = vec![
|
|
"[site+0x04] restored from 0x0045c150 -> 0x0045c310 -> 0x0045c36e -> 0x00456100 -> 0x00455b70 -> 0x0052edf0"
|
|
.to_string(),
|
|
"[site+0x2a8] rewritten by 0x0040f6d0 after 0x00481390 returns the linked peer id"
|
|
.to_string(),
|
|
"[peer+0x08] refreshed during 0x00444690 -> 0x004133b0 -> 0x0040ee10 -> 0x0040edf6 -> 0x00480710"
|
|
.to_string(),
|
|
"world-cell owner and linked-site chains [cell+0xd4]/[cell+0xd6] republished during 0x00480710 via 0x0042bbf0/0x0042bbb0 and 0x0042c9f0/0x0042c9a0"
|
|
.to_string(),
|
|
];
|
|
let near_city_acquisition_region_input_fields = vec![
|
|
"[site+0x276] owner-present gate".to_string(),
|
|
"placed-structure subject subtype gate [candidate+0x32] == 4 consumed through 0x0040d360"
|
|
.to_string(),
|
|
"[site+0x3d5] age/year delta lane".to_string(),
|
|
"[site+0x310/+0x338/+0x360] cached tri-lane sampled through 0x0040cac0".to_string(),
|
|
"[site+0x2a4] self placed-structure id lane later consumed through 0x004269b0".to_string(),
|
|
];
|
|
let near_city_acquisition_peer_input_fields = vec![
|
|
"center-cell token gate 0x0041f6e0 -> 0x0042b2d0 over the current region".to_string(),
|
|
"[site+0x04] live backing-record selector consumed by 0x0047efe0 / 0x0047fd50".to_string(),
|
|
"0x0047fd50 linked-peer candidate-class gate over [candidate+0x8c] accepting only 0/1/2".to_string(),
|
|
"[site+0x2a8] linked peer-site id consumed by 0x0040d1f0".to_string(),
|
|
"[peer+0x08] route-entry anchor id consumed by 0x0047dda0".to_string(),
|
|
"world-cell owner chain [cell+0xd4] and linked-site chain [cell+0xd6] republished by 0x00480710".to_string(),
|
|
"linked-region status branch 0x0047de00 -> 0x0040c990".to_string(),
|
|
];
|
|
let near_city_acquisition_company_input_fields = vec![
|
|
"company stat-family reader 0x2329/0x0d through 0x0042a5d0".to_string(),
|
|
"save-native linked-transit route-anchor entry id [company+0x0d35] through 0x00401860"
|
|
.to_string(),
|
|
"save-native linked-transit route-anchor fallback counts [company+0x7664/+0x7668/+0x766c] through 0x00401860"
|
|
.to_string(),
|
|
"current chairman profile byte [profile+0x291] through 0x00426ef0".to_string(),
|
|
"company byte [company+0x5b] and indexed lane [company+0x67 + 12*0x0042a0e0()]".to_string(),
|
|
"company-root argument [company+0x00] passed into 0x0040d540 and 0x00455f60".to_string(),
|
|
];
|
|
let near_city_acquisition_shellless_readiness_status =
|
|
"peer_and_company_inputs_grounded_site_owner_and_tri_restore_gaps_remaining".to_string();
|
|
let near_city_acquisition_runtime_backed_input_families = vec![
|
|
"peer-site restore subset [site+0x3cc/+0x3d0] plus tagged 0x5dc1 [owner+0x23e/+0x242]"
|
|
.to_string(),
|
|
"peer-site bring-up replay path reconstructing [site+0x04], [site+0x2a8], and [peer+0x08]"
|
|
.to_string(),
|
|
"linked-site post-load replay republishing world-cell owner and linked-site chains through 0x0042bbf0/0x0042bbb0 and 0x0042c9f0/0x0042c9a0"
|
|
.to_string(),
|
|
"placed-structure linked-company resolver 0x0047efe0 already grounds the live owner-company meaning of [site+0x276]"
|
|
.to_string(),
|
|
"placed-structure peer-chain helpers 0x0041f7e0 / 0x0041f810 / 0x0041f850 already ground [site+0x2a4] as the record's own placed-structure id lane"
|
|
.to_string(),
|
|
"0x004269b0 resolves the chosen site id back through live placed-structure collection 0x0062b26c before mutating [site+0x276], so the [site+0x2a4] self-id lane is reconstructible from collection identity once the chosen live row is known"
|
|
.to_string(),
|
|
"aux-candidate stream-load and stem-policy chain 0x004131f0 -> 0x00412fb0 -> 0x004120b0 -> 0x00412ab0 already grounds the subtype byte consumed as [candidate+0x32]"
|
|
.to_string(),
|
|
"direct site constructor 0x004134d0 allocates through 0x00518900 and seeds broad row state through 0x0040f6d0, including [site+0x2a4], copied name bytes, [site+0x276], [site+0x3d4/+0x3d5], and cleared tri-lane-adjacent caches"
|
|
.to_string(),
|
|
"shared finalize helper 0x0040ef10 now has both create-side callers 0x00403ef3 / 0x00404489 and data-driven loader callers 0x0046f073 / 0x004707ff, so the [site+0x276] owner lane is grounded under constructor-plus-finalize families rather than only the post-load local replay strip"
|
|
.to_string(),
|
|
"data-driven loader callers 0x0046f073 / 0x004707ff push tuple fields [+0x00/+0x04/+0x0c] into 0x0040ef10, and that helper's third argument flows into ebx and then [site+0x276] at 0x0040f5d4"
|
|
.to_string(),
|
|
"at least one of those tuple-backed callers is now classified too: 0x004707ff sits under multiplayer transport selector-0x13 body 0x004706b0 rather than the ordinary save-load restore strip"
|
|
.to_string(),
|
|
"another tuple-backed 0x004134d0 family is classified too now: 0x00472b40 is the multiplayer transport selector-0x72 counted live-world apply path, and its inner builders 0x00472bef / 0x00472d03 reach 0x004134d0 from counted transport records rather than ordinary save-load restore"
|
|
.to_string(),
|
|
"non-transport caller 0x00422bb4 also reaches 0x004134d0, but it pushes live args plus literal flags 1/0 and returns the created row id through an out-param instead of feeding the tuple-backed finalize path"
|
|
.to_string(),
|
|
"the surviving 0x00508fd1 / 0x005098eb family is bounded away from persisted restore too: it caches the created site id in [this+0x7c], re-enters 0x0040eba0 with immediate coords, and later calls 0x0040ef10 with a hard zero third arg"
|
|
.to_string(),
|
|
"0x00473c20 is a separate live queue-drain family over scratch band 0x006ce808..0x006ce988: it iterates queued site ids and coordinate pairs, re-enters 0x0040eba0 at 0x00473c98, then clears each queued id, so it is a local post-create refresh path rather than a persisted replay owner"
|
|
.to_string(),
|
|
"the remaining direct [site+0x276] store census is bounded away from persisted replay too: 0x0042128d is broad zero-init in the 0x00421430 constructor neighborhood, 0x00422305 computes a live score/category lane before publishing 0x7, 0x004269c9/0x00426a2a are acquisition commit and clear helpers, and 0x004282a9/0x004300d6 are bulk owner-transfer writes"
|
|
.to_string(),
|
|
"the paired collection-side loader 0x00413440 is bounded away too: it owns the tagged 0x36b1/0x36b2/0x36b3 triplet load path, dispatches each live record through vtable slot +0x44, and keeps that seam on the already-grounded triplet payload rather than the missing [site+0x276] replay owner"
|
|
.to_string(),
|
|
"station-detail mutation path 0x0040dc40 already consumes [site+0x276], company stat-family 0x2329/0x0d, and candidate field [candidate+0x22], then commits linked-site side-state rebuild through 0x0040d1f0 / 0x00480710 / 0x0045b160 / 0x0045b9b0 / 0x00418be0 / 0x0040cd70"
|
|
.to_string(),
|
|
"city-connection direct-placement family 0x00402cb0 -> 0x00403ed5/0x0040446b -> 0x004134d0 -> 0x0040ef10 already grounds the shared allocator/finalize path for newly created site rows"
|
|
.to_string(),
|
|
"opcode-dispatch strip 0x00431b20 routes grouped 0x0061039c opcodes into the same live site-mutation helpers 0x00430040 / 0x00426d60 / 0x0042fc90 rather than the bring-up replay family"
|
|
.to_string(),
|
|
"direct local writer strip now grounds live cached-tri-lane producers too: 0x0040d450 writes [site+0x310] through owner-company-aware scoring, and the broader candidate-processing loop 0x00410b30..0x004118f4 writes [site+0x310/+0x338/+0x360] after gating rows through 0x00412560"
|
|
.to_string(),
|
|
"company stat-family lane 0x2329/0x0d already rehosted in runtime company state".to_string(),
|
|
"company market state now carries the save-native linked-transit route-anchor tuple [company+0x0d35] and [company+0x7664/+0x7668/+0x766c]"
|
|
.to_string(),
|
|
"chairman personality byte [profile+0x291] already reconstructed from raw save chairman rows"
|
|
.to_string(),
|
|
"company-root pointer and linked chairman/company save-native roster identity already imported"
|
|
.to_string(),
|
|
];
|
|
let near_city_acquisition_site_owner_company_projection_status =
|
|
"ordinary_replay_ruled_down_stream_load_callback_grounded_tuple_finalize_path_grounded_nontransport_restore_source_missing"
|
|
.to_string();
|
|
let near_city_acquisition_site_self_id_projection_status =
|
|
"live_meaning_grounded_reconstructible_from_collection_identity".to_string();
|
|
let near_city_acquisition_site_cached_tri_lane_projection_status =
|
|
"live_writer_family_grounded_semantics_and_persisted_inputs_missing".to_string();
|
|
let near_city_acquisition_tri_lane_live_service_status =
|
|
"candidate_gate_and_live_writer_family_grounded_exact_formula_and_persisted_inputs_missing"
|
|
.to_string();
|
|
let near_city_acquisition_candidate_subtype_projection_status =
|
|
"cached_candidate_id_bridge_grounded_via_stream_load".to_string();
|
|
let near_city_acquisition_backing_record_projection_status =
|
|
"stream_load_callback_grounded_via_0x40ce60".to_string();
|
|
let near_city_acquisition_nontransport_persisted_source_status =
|
|
"ordinary_runtime_effect_candidate_present_trigger_lane_mapping_missing".to_string();
|
|
let near_city_acquisition_nontransport_persisted_source_candidates = vec![
|
|
"ordinary loaded runtime-effect lane 0x00444d92 -> 0x00432f40(kind 8) -> 0x004323a0 -> 0x00431b20".to_string(),
|
|
"non-direct runtime-event bundle 0x4e99/0x4e9a/0x4e9b decodes grouped placed-structure descriptors on checked maps".to_string(),
|
|
"restore-side loader 0x00433130 with 0x0042db20 repopulates ordinary live runtime-effect rows in 0x0062be18".to_string(),
|
|
"trigger-kind control lane [event+0x7ef] is editor-visible across 0x00..0x0a including kind 8".to_string(),
|
|
"remaining gap is which serialized/live rows feed trigger kind 8 into that lane and which loaded ordinary rows actually reach placed-structure mutation opcodes".to_string(),
|
|
];
|
|
let near_city_acquisition_tri_lane_save_shape_family_candidates =
|
|
summarize_near_city_acquisition_tri_lane_save_shape_family_candidates(analysis);
|
|
let near_city_acquisition_tri_lane_save_shape_family_status =
|
|
if near_city_acquisition_tri_lane_save_shape_family_candidates.is_empty() {
|
|
"save_shape_family_probe_missing".to_string()
|
|
} else {
|
|
"save_shape_family_candidates_present_fixed_offset_ruled_down".to_string()
|
|
};
|
|
let near_city_acquisition_tri_lane_live_owner_families = vec![
|
|
"0x0040d450 owner-company-aware local scorer producing [site+0x310]".to_string(),
|
|
"0x00410b30..0x004118f4 broader candidate-processing loop producing [site+0x310/+0x338/+0x360]"
|
|
.to_string(),
|
|
"0x00412560 shared candidate/admissibility gate above both scorer paths".to_string(),
|
|
"0x0040c9a0 deferred additive accumulator/reset folding the tri-lane into [site+0x2b4/+0x2b8/+0x2bc] and [site+0x2e4..]"
|
|
.to_string(),
|
|
"0x0040fcc0..0x0040fe28 and 0x00422c62..0x00422d3c weighted scoring/evaluation consumers reading 0x0040cac0"
|
|
.to_string(),
|
|
];
|
|
let near_city_acquisition_tri_lane_candidate_gate_fields = vec![
|
|
"0x00412560 gates candidate rows using fields [+0x20/+0x22/+0x24/+0x28/+0x2c/+0x44]".to_string(),
|
|
"0x00412560 consumes world date/flags through 0x006cec78".to_string(),
|
|
"0x00412560 resolves candidate rows from table 0x0062ba8c".to_string(),
|
|
"0x00412560 callers pass the placed-structure subject vtable slot +0x80 result plus owner-present flag from [site+0x246]".to_string(),
|
|
"direct callers of 0x00412560 are now bounded at 0x0040fb8d, 0x00410721, 0x00410b71, 0x00412620, and 0x004126d3".to_string(),
|
|
];
|
|
let near_city_acquisition_tri_lane_runtime_writer_roles = vec![
|
|
"0x0040d450 adds one owner-company-aware local score component into [site+0x310]".to_string(),
|
|
"0x00410b30..0x004118f4 walks 0xbc-stride candidate rows and adds per-row score components into [site+0x310/+0x338/+0x360]".to_string(),
|
|
"0x0041114a7/0x004111572 add into [site+0x310] and 0x0041114b7/0x004111582 add into [site+0x338]".to_string(),
|
|
"0x0041118aa/0x0041118f4 add into [site+0x360]".to_string(),
|
|
"0x0040c9a0 later folds the tri-lane into [site+0x2b4/+0x2b8/+0x2bc] and clears the transient producer lanes".to_string(),
|
|
];
|
|
let near_city_acquisition_tri_lane_direct_caller_families = vec![
|
|
"0x0040fb70 is a small wrapper passing one candidate row plus the subject vtable slot +0x80 result and owner-present flag into 0x00412560".to_string(),
|
|
"0x004b4052 / 0x004b46ec are collection-wide 0x0040fb70 callers iterating 0x0062b26c candidate rows".to_string(),
|
|
"0x00401633 is an acquisition-adjacent 0x0040d540 caller that immediately feeds company stat-family 0x2329/0x0d".to_string(),
|
|
"0x0044b81a is an owner-company-aware 0x0040d540 caller that also reaches 0x0040cb70 and 0x00436590 news/event id 0x65".to_string(),
|
|
"0x004b70f5 / 0x004b7979 are broader 0x0040d540 callers routing through 0x004337a0 and downstream 0x00540120 / 0x00518140 state consumers".to_string(),
|
|
];
|
|
let near_city_acquisition_tri_lane_formula_input_lanes = vec![
|
|
"0x00412560 uses candidate-row time window [+0x20/+0x22], owner/absence booleans [+0x24/+0x28], list count [+0x2c], and membership list [+0x44]".to_string(),
|
|
"0x00412560 consumes world date and policy flags through 0x006cec78 fields [world+0x0d] and [world+0x4afb]".to_string(),
|
|
"0x0040d450 combines helper outputs 0x00455810 / 0x00455800 / 0x0044ad60 with owner-company lane [site+0x276] and world event sink 0x00436590 ids 0x66/0x68".to_string(),
|
|
"0x00410b30..0x004118f4 consumes candidate-row fields [+0x18/+0x1c/+0x2a/+0x2c/+0x44], subject latch [site+0x78c], and personality byte [site+0x391]".to_string(),
|
|
"0x00410b30..0x004118f4 also feeds world-side scalar [world+0x4caa], owner-company scalar [company+0x0d5d] through 0x0040d210, and the local cache bands [site+0x2e8], [site+0x310], [site+0x338], and [site+0x360]".to_string(),
|
|
];
|
|
let near_city_acquisition_projection_hypotheses = vec![
|
|
SmpServiceConsumerHypothesis {
|
|
label: "site_owner_replay_from_post_load_refresh_self_id_reconstructible".to_string(),
|
|
status: "ordinary_replay_and_stream_load_ruled_down_tuple_finalize_positive_path_grounded".to_string(),
|
|
candidate_consumers: vec![
|
|
"0x00444690 late world bring-up caller".to_string(),
|
|
"0x004133b0 placed-structure local-runtime replay owner".to_string(),
|
|
"0x0040ee10 live-site position/scalar refresh helper".to_string(),
|
|
"0x00480710 linked-site runtime side-buffer and route-entry refresh".to_string(),
|
|
"0x004134d0 allocator plus direct site constructor 0x0040f6d0".to_string(),
|
|
"0x00403ef3 / 0x00404489 create-side callers of shared finalize helper 0x0040ef10"
|
|
.to_string(),
|
|
"0x0046f073 / 0x004707ff data-driven loader callers of shared finalize helper 0x0040ef10"
|
|
.to_string(),
|
|
"0x004269b0 acquisition commit owner resolving live site rows by id".to_string(),
|
|
],
|
|
evidence: vec![
|
|
"[site+0x276] live owner-company meaning is grounded through 0x0047efe0 / 0x0040d210".to_string(),
|
|
"[site+0x2a4] self-id meaning is grounded through 0x0041f7e0 / 0x0041f810 / 0x0041f850".to_string(),
|
|
"0x004269b0 resolves the chosen site id through placed-structure collection 0x0062b26c before mutating the live row, so [site+0x2a4] is reconstructible from collection identity rather than a separate serializer-owned selector".to_string(),
|
|
"0x00444690 -> 0x004133b0 -> 0x0040ee10 is the current checked-in ordinary bring-up replay family above live placed structures".to_string(),
|
|
"the ordinary restore-side staging order is tighter now too: world bring-up calls 0x00413280 first for tagged 0x36b1/0x36b2/0x36b3 stream load at 0x00444467, refreshes dynamic side buffers through 0x00481210 at 0x004444d8, and only later enters 0x004133b0 at 0x00444690 for queued local-runtime replay plus 0x0040ee10".to_string(),
|
|
"0x004133b0 rebuilds cloned local-runtime records through 0x0040e450 and 0x0040ee10 only republishes local position/scalar triplets before 0x0040e360".to_string(),
|
|
"the ordinary bring-up strip stays separate from the constructor/finalize family too: after 0x00444690 -> 0x004133b0 the world restore continues through later route-entry/grid/tagged refresh owners rather than re-entering 0x004134d0 / 0x0040f6d0 / 0x0040ef10 for already-restored rows".to_string(),
|
|
"[site+0x27a] is now bounded as a live signed scalar accumulator rather than a second owner-identity mystery: base constructor 0x00421200 zeros it at 0x0042125d, create-side initializer 0x0040f6d0 zeros it again at 0x0040f793, station-detail apply 0x0040dfec accumulates into it before 0x0040d1f0 / 0x00480710, acquisition commit stores it at 0x004269e4, acquisition clear negates and clears it through 0x00426a44..0x00426a90, and acquisition delta helper 0x00426ad8 accumulates into it again".to_string(),
|
|
"direct local replay-strip inspection now splits that family more precisely: bounded 0x0040ee10 itself only reads cached source lane [site+0x3cc], and the bounded 0x00480710 neighborhood is working from [site+0x04], [site+0x08], and [site+0x3cc]; the broader immediate continuation 0x0040e360..0x0040edf6 still consumes [site+0x2a8], [site+0x2a4], and [site+0x276] around 0x0040d230 / 0x0040d1f0 / 0x00480710 / 0x00426b10 / 0x00455860, but in the checked range those [site+0x276] uses are still reads/queries rather than a direct rehydrating store".to_string(),
|
|
"direct constructor control-flow now shows 0x004134d0 allocating a new placed-structure row through 0x00518900 and then calling 0x0040f6d0, which seeds [site+0x2a4], clears broad row state, copies the display name bytes, and writes [site+0x276] from an incoming argument before any later service logic runs".to_string(),
|
|
"0x0040f6d0 immediately zeroes [site+0x2a8/+0x272/+0x27a/+0x29e], stamps [site+0x3d4/+0x3d5], and seeds further local caches, which makes it a create-side initializer rather than a replay-only refresh".to_string(),
|
|
"shared finalize helper 0x0040ef10 now has create-side callers 0x00403ef3 / 0x00404489 and data-driven callers 0x0046f073 / 0x004707ff; the latter feed it from tuple-backed loads after 0x0040eba0 / 0x0052eb90 rather than from the checked-in local replay strip".to_string(),
|
|
"the loader-side dataflow is narrower now too: 0x0046efbf is the paired constructor call and 0x0046f073 / 0x004707ff are the paired finalize calls in the tuple-backed data path; 0x0046efbf and 0x0047074b both reach 0x004134d0 first, then 0x0046f073 / 0x004707ff push tuple fields [+0x00/+0x04/+0x0c] into 0x0040ef10, that helper reads arg3 into ebx at 0x0040ef1c, and the paired write at 0x0040f5d4 stores ebx into [site+0x276] while 0x0040f5da stores the computed companion word into [site+0x27a]".to_string(),
|
|
"the outer owner above 0x0046efbf / 0x0046f073 / 0x0047074b / 0x004707ff is now classified too: atlas-backed recovery ties those calls to multiplayer transport selector-0x13 body 0x004706b0, which attempts the placed-structure apply path through 0x004197e0 / 0x004134d0 / 0x0040eba0 / 0x0052eb90 / 0x0040ef10 rather than ordinary save-load restore".to_string(),
|
|
"the neighboring builder strip 0x00472b40 is classified too now: atlas-backed recovery ties it to multiplayer transport selector-0x72, the heavier counted live-world apply path over 0x0062b2fc / 0x0062b26c / 0x0062bae0, and its inner calls 0x00472bef / 0x00472d03 reach 0x004134d0 from counted transport records rather than ordinary save-load restore".to_string(),
|
|
"another surviving 0x004134d0 caller is bounded away from persisted restore too: 0x00422bb4 pushes one live 0x0062b2fc record plus local args and literal flags 1/0 into 0x004134d0, then returns the created row id through an out-param rather than re-entering the tuple-backed finalize path".to_string(),
|
|
"the remaining 0x00508fd1 / 0x005098eb strip is bounded away from persisted restore too: 0x00508fd1 stores the new row id in [this+0x7c], immediately configures the live row through vtable slot +0x58 plus 0x00507cf0, and 0x005098eb later re-enters 0x0040ef10 with arg3 forced to zero, so this family is another live controller path rather than the missing persisted owner seam".to_string(),
|
|
"the adjacent 0x00473c20 family is bounded away too: it drains queued site ids and coordinate pairs from scratch band 0x006ce808..0x006ce988, re-enters 0x0040eba0 at 0x00473c98 for each live row, and then clears the queued id slot, so it is a local post-create refresh path rather than the missing persisted owner seam".to_string(),
|
|
"the remaining direct [site+0x276] store census is bounded away too: 0x0042128d is broad zero-init in the 0x00421430 constructor neighborhood, 0x00422305 computes a live score/category lane before publishing event 0x7, 0x004269c9/0x00426a2a are acquisition commit and clear helpers, and 0x004282a9 / 0x004300d6 are bulk owner-transfer writes rather than ordinary restored-row replay".to_string(),
|
|
"the paired collection-side serializer 0x00413440 is bounded away too: it opens tagged families 0x36b1 / 0x36b2 / 0x36b3 on the save side, routes each live record through per-entry vtable slot +0x44, and keeps that seam on the already-grounded triplet payload/load-save strip rather than the missing [site+0x276] replay owner".to_string(),
|
|
"the actual broader restore-side stream-load owner remains 0x00413280: it opens tagged 0x36b1 / 0x36b2 / 0x36b3 on the load side, dispatches per-entry vtable slot +0x40, and currently only grounds the cached-source bridge 0x0040ce60 -> 0x0040cd70 / 0x0045c150 rather than a direct [site+0x276] republisher".to_string(),
|
|
"direct local control flow now rules the ordinary bring-up owner down even further: 0x00444690 immediately calls 0x004133b0 and then continues into later grid/world refresh owners without re-entering 0x004134d0 / 0x0040f6d0 / 0x0040ef10 for already-restored rows".to_string(),
|
|
"inside 0x0040ef10 the [site+0x276] write at 0x0040f047 only clears owner-company under a world-flag branch, while the paired [site+0x276]/[site+0x27a] write at 0x0040f5d4 follows a 0x00436590 event/scalar path and is not the generic post-load republisher".to_string(),
|
|
"direct local writer census now shows the grounded [site+0x276] write side clustering under live mutation families such as 0x004269b0 / 0x00426a10, the create-side 0x0040ef10 / 0x0040f6d0 strip, and the bulk reassignment families 0x00426dce..0x00426ea1 and 0x00430040..0x004300d6 rather than under the known replay strip".to_string(),
|
|
"direct local control-flow reconstruction now shows those same writer families hanging under the 0x00431b20 opcode dispatcher over 0x0061039c: opcodes 0x04..0x07 dispatch to 0x00430040, opcodes 0x08/0x10..0x13 dispatch to 0x00426d60, and opcodes 0x0d/0x16 dispatch to 0x0042fc90".to_string(),
|
|
"0x0042fc90 itself iterates the live placed-structure collection 0x0062b26c, filters rows through 0x0040c990 plus optional company match [site+0x276], and then dispatches row vtable slot +0x70, which keeps that branch on the live application side rather than replay".to_string(),
|
|
"the trigger-kind field itself is now bounded as an ordinary loaded per-event lane rather than a startup-only special class: restore-side loader 0x00433130 repopulates live event collection 0x0062be18 from packed chunk family 0x4e21/0x4e22, and the event-detail editor strip 0x004d90ba..0x004d91ed writes [event+0x7ef] across the full 0x00..0x0a range through controls 0x4e98..0x4ea2, including kind 8 at 0x004d91b3".to_string(),
|
|
"that keeps 0x00444d92 -> 0x00432f40(kind 8) on the ordinary loaded runtime-effect pipeline too: world bring-up is servicing pre-existing rows from 0x0062be18 rather than a one-off startup-only record class synthesized outside the collection".to_string(),
|
|
"the event-detail editor family now ties that trigger-kind field to the ordinary runtime-effect builders too: selected-event control family 0x004db02a / 0x004db1b8..0x004db309 mirrors current [event+0x7ef] back into controls 0x4e98..0x4ea2 under root control 0x4e84, while editor-side builder 0x004db9e5..0x004db9f1 allocates a runtime-effect row from compact payload into 0x0062be18 through 0x00432ea0 before rebinding the selected event id".to_string(),
|
|
"bundle-side inspection now grounds the ordinary startup collection further too: the non-direct 0x4e99/0x4e9a/0x4e9b runtime-event collection decodes as a compact serializer family recovered from 0x00433060/0x00430d70 plus the paired 0x00433130/0x0042db20 load path rather than an opaque raw blob, and sampled maps such as War Effort/British Isles/Germany/Texas Tea now decode their compact rows into actual condition/grouped summaries instead of signature-only parity".to_string(),
|
|
"the adjacent control-lane owner is bounded too now: nearby helper 0x0042e050 copies text bands plus [event+0x7ee..0x80f] between live runtime-event rows, which separates full event-control cloning from the narrower compact row-body loader 0x0042db20".to_string(),
|
|
],
|
|
blockers: vec![
|
|
"current atlas evidence now grounds one tuple-backed owner path too: loader tuple field [+0x0c] reaches [site+0x276] through 0x0046f073 / 0x004707ff -> 0x0040ef10, but the classified 0x004707ff caller belongs to multiplayer transport selector-0x13 rather than ordinary save-load restore, so a non-transport persisted source family is still needed for shellless acquisition".to_string(),
|
|
"the explicit store census now also rules down the remaining obvious non-transport writes, so the missing ordinary restored-row owner seam likely sits outside the currently bounded direct allocator/finalize/store families".to_string(),
|
|
"the paired collection-side triplet serializer 0x00413440 is ruled down too, so the missing ordinary restored-row owner seam likely sits outside the currently bounded direct allocator/finalize/store families and the tagged 0x36b1/0x36b2/0x36b3 load-save strip".to_string(),
|
|
"the load-side stream owner 0x00413280 is ruled down to cached-source/candidate replay through vtable slot +0x40 and 0x0040ce60, so the missing ordinary restored-row owner seam still sits beyond the current stream-load bridge too".to_string(),
|
|
"the ordinary bring-up caller 0x00444690 is ruled down too: it just enters 0x004133b0 and then proceeds into later refresh owners, so the positive [site+0x276] write side at 0x0040ef10 remains a tuple/create path rather than the checked ordinary restore path".to_string(),
|
|
"the checked ordinary restore ordering is ruled down too: 0x00413280 stream load, 0x00481210 dynamic side-buffer refresh, and 0x004133b0 local-runtime replay all sit on the bring-up strip without re-entering 0x004134d0 / 0x0040f6d0 / 0x0040ef10 for already-restored rows".to_string(),
|
|
"the grouped opcode dispatcher 0x00431b20 is still not a tagged restore owner, but the remaining uncertainty is now narrower than compact row framing too: restore-side 0x00433130 with 0x0042db20 reloads compact row bodies into ordinary live event rows in 0x0062be18, nearby 0x0042e050 is the separate full-event copy owner for [event+0x7ee..0x80f], the event-detail editor exposes [event+0x7ef] across 0x00..0x0a including kind 8, and sampled map bundles now decode into concrete grouped descriptors, so the open question is which serialized/live rows feed trigger kind 8 into that control lane and which of those loaded rows can actually reach the placed-structure mutation opcodes under 0x00431b20".to_string(),
|
|
],
|
|
},
|
|
SmpServiceConsumerHypothesis {
|
|
label: "site_cached_tri_lane_payload_or_restore_owner".to_string(),
|
|
status: "checked_in_save_seams_ruled_down_live_scoring_family_grounded_exact_semantics_open".to_string(),
|
|
candidate_consumers: vec![
|
|
"0x36b1/0x36b2/0x36b3 placed-structure triplet owner".to_string(),
|
|
"0x00455fc0 shared tagged payload loader".to_string(),
|
|
"0x00444690 -> 0x004133b0 replay strip".to_string(),
|
|
"0x0040d450 owner-company-aware local scorer writing [site+0x310]".to_string(),
|
|
"0x00410b30..0x004118f4 candidate-processing loop writing [site+0x310/+0x338/+0x360] after 0x00412560".to_string(),
|
|
],
|
|
evidence: vec![
|
|
"0x0040cac0 is only the exact raw delta reader over [site+0x310/+0x338/+0x360]".to_string(),
|
|
"direct local binary inspection now shows 0x0040c9a0 as the deferred additive accumulator over [site+0x310/+0x338/+0x360], folding that tri-lane into [site+0x2b4/+0x2b8/+0x2bc], mirroring the nine-dword side array rooted at [site+0x2e4], and then clearing the tri-lane".to_string(),
|
|
"direct local caller census now shows 0x0040c9a0 only under the broad live-collection sweep 0x0040a3a1..0x0040a4d3, while 0x0040cac0 stays under weighted scoring or evaluation families such as 0x0040fcc0..0x0040fe28 and 0x00422c62..0x00422d3c".to_string(),
|
|
"direct local binary inspection now shows concrete live producers too: 0x0040d4aa/0x0040d4b0 add into [site+0x310], 0x0041114a7/0x004111572 add into [site+0x310], 0x0041114b7/0x004111582 add into [site+0x338], and 0x0041118aa/0x0041118f4 add into [site+0x360]".to_string(),
|
|
"0x0040d450 is a small owner-company-aware producer over [site+0x276], 0x00455810/0x00455800/0x0044ad60, and 0x00436590 ids 0x66/0x68 that writes directly into [site+0x310]".to_string(),
|
|
"0x00410b30..0x004118f4 is a broader candidate-processing loop walking 0xbc-stride rows, gating them through 0x00412560, and then accumulating stack temporaries plus direct writes into [site+0x310/+0x338/+0x360]".to_string(),
|
|
"0x00412560 itself is a shared candidate/admissibility gate over candidate-row fields [+0x20/+0x22/+0x24/+0x28/+0x2c/+0x44], world date/flags via 0x006cec78, and the candidate table 0x0062ba8c".to_string(),
|
|
"current checked-in save owners still do not serialize those lanes directly, which rules down the known save seams while leaving a later restore family open".to_string(),
|
|
"0x00481430 -> 0x0047d8e0 repopulates the dynamic side-buffer route-entry list, three byte arrays, five proximity buckets, and trailing scratch band from stream without claiming the tri-lane".to_string(),
|
|
],
|
|
blockers: vec![
|
|
"no checked-in triplet or side-buffer payload field is tied directly to the tri-lane, and the remaining open question is now the exact service semantics and persisted inputs of the grounded live scorer family rather than the existence of runtime writers".to_string(),
|
|
],
|
|
},
|
|
SmpServiceConsumerHypothesis {
|
|
label: "cached_source_candidate_id_to_subtype_projection".to_string(),
|
|
status: "grounded_stream_load_callback_0x40ce60".to_string(),
|
|
candidate_consumers: vec![
|
|
"0x0045c150 -> 0x0045c310 -> 0x0045c36e -> 0x00456100 -> 0x00455b70".to_string(),
|
|
"0x005c8c50 +0x40 stream-load callback 0x0040ce60".to_string(),
|
|
"0x0040cd70 cached source/candidate resolver seeding [site+0x3cc/+0x3d0]".to_string(),
|
|
"0x0040cee0 cached candidate resolver through 0x0062b268".to_string(),
|
|
"0x004131f0 -> 0x00412fb0 -> 0x004120b0 -> 0x00412ab0 aux-candidate load chain".to_string(),
|
|
],
|
|
evidence: vec![
|
|
"[site+0x04] backing-record selector owner is grounded, but the stronger checked-in bridge is now [site+0x3cc/+0x3d0]".to_string(),
|
|
"direct local binary inspection now shows the placed-structure stream-load path 0x00413280 dispatching per-entry vtable slot +0x40 on the 0x005c8c50 specialization table, and that slot resolves to 0x0040ce60".to_string(),
|
|
"0x0040ce60 canonicalizes the Radio Station stem and then re-enters 0x0040cd70 plus 0x0045c150 on stream load".to_string(),
|
|
"0x0040cd70 rebuilds cached source id [site+0x3cc] and candidate/profile id [site+0x3d0]".to_string(),
|
|
"0x0040cee0 resolves cached candidate id [site+0x3d0] back into the live candidate pool 0x0062b268".to_string(),
|
|
"0x004138f0 already counts live placed structures by cached candidate id [site+0x3d0], confirming that lane as a real live selector bridge".to_string(),
|
|
"candidate subtype ownership is bounded under the aux-candidate load and stem-policy chain".to_string(),
|
|
"0x0040d360 only consumes the loaded candidate subtype byte [candidate+0x32] == 4".to_string(),
|
|
],
|
|
blockers: Vec::new(),
|
|
},
|
|
];
|
|
let near_city_acquisition_remaining_owner_gaps = vec![
|
|
"non-transport persisted source family outside the currently bounded direct allocator/finalize/store families and tagged 0x36b1/0x36b2/0x36b3 load path, feeding the tuple-to-live projection of placed-structure owner-company field [site+0x276] for the acquisition-side owner-present gate; the live meaning is grounded through 0x0047efe0, the owner family is bounded under 0x004134d0 / 0x0040f6d0 plus shared finalize helper 0x0040ef10, and loader tuple field [+0x0c] is known to seed [site+0x276] through 0x0046f073 / 0x004707ff, but the classified 0x004707ff caller sits under multiplayer transport selector-0x13 rather than ordinary save-load restore".to_string(),
|
|
"exact persisted inputs and shellless service semantics for the now-grounded live cached tri-lane writer family over [site+0x310/+0x338/+0x360], especially 0x0040d450 and 0x00410b30..0x004118f4 above 0x00412560".to_string(),
|
|
];
|
|
let near_city_acquisition_region_lane_statuses = vec![
|
|
"[site+0x276] owner-present gate: consumed directly by 0x004014b0, the live owner-company meaning is grounded through 0x0047efe0, the write family is bounded under 0x004134d0 / 0x0040f6d0 plus shared finalize helper 0x0040ef10, loader tuple field [+0x0c] is known to seed that lane through 0x0046f073 / 0x004707ff, and the tagged 0x36b1/0x36b2/0x36b3 collection loader is ruled down; the remaining gap is which non-transport persisted source family and companion restore calls outside those bounded families are sufficient for shellless acquisition".to_string(),
|
|
"[site+0x2a4] placed-structure id lane: peer-chain helpers already ground this as the record's own site id, constructor-side 0x00480210 seeds it for new linked-site rows, and 0x004269b0 resolves the chosen site id back through 0x0062b26c before mutating [site+0x276], so this lane is reconstructible from collection identity for the 0x004014b0 commit path".to_string(),
|
|
"[site+0x310/+0x338/+0x360] cached tri-lane: exact delta reader grounded at 0x0040cac0, deferred additive accumulator/reset helper grounded at 0x0040c9a0, and direct live producers now grounded at 0x0040d450 plus the broader 0x00410b30..0x004118f4 candidate-processing loop above 0x00412560; the remaining gap is exact service semantics and persisted inputs, not writer existence".to_string(),
|
|
"placed-structure subtype filter: 0x0040d360 is the exact test [candidate+0x32] == 4, the owning subtype byte is already bounded under 0x004131f0 -> 0x00412fb0 -> 0x004120b0 -> 0x00412ab0, and direct local binary inspection now grounds stream-load callback 0x0040ce60 as the restore-side bridge into [site+0x3cc/+0x3d0]".to_string(),
|
|
];
|
|
let atlas_candidate_consumers = vec![
|
|
"0x004019e0 periodic company outer service owner".to_string(),
|
|
"0x00406050 city-connection bonus/news owner".to_string(),
|
|
"0x00402cb0 city-connection direct-placement builder owner".to_string(),
|
|
"0x00409950 linked-transit train-roster balancer".to_string(),
|
|
"0x004014b0 near-city industry acquisition and news owner".to_string(),
|
|
"0x0040dc40 station-detail linked-site mutation validator/apply owner".to_string(),
|
|
"0x00401c50 annual finance-policy owner".to_string(),
|
|
"0x00420030 / 0x00420280 peer-site boolean/selector pair over 0x006cec20".to_string(),
|
|
"0x004093d0 / 0x00407bd0 linked-transit refresh tails".to_string(),
|
|
];
|
|
let known_bridge_helpers = vec![
|
|
"0x004078a0 preferred-locomotive chooser feeding company byte 0x0d17".to_string(),
|
|
"0x0041d550 locomotive-era and engine-type approval gate over scenario opinion lanes"
|
|
.to_string(),
|
|
"0x004010f0 near-city acquisition region scorer over class-0 region entries".to_string(),
|
|
"0x00405920 same-company industry proximity aggregator over linked site peers".to_string(),
|
|
"0x0041f6e0 center-cell token gate feeding 0x0042b2d0 over the current region".to_string(),
|
|
"0x0042b2d0 packed-u16 contains-key predicate over the region token list".to_string(),
|
|
"0x0047de00 linked-region resolver feeding the candidate status branch 0x0040c990"
|
|
.to_string(),
|
|
"0x004801a0 linked-transit route-anchor reachability gate for one candidate site"
|
|
.to_string(),
|
|
"0x00425b90 pending-bonus/company-state gate over the [region+0x276] companion object"
|
|
.to_string(),
|
|
"0x0040cac0 placed-structure cached tri-lane delta sampler over [site+0x310/+0x338/+0x360]"
|
|
.to_string(),
|
|
"0x0040c9a0 deferred additive accumulator/reset folding [site+0x310/+0x338/+0x360] into [site+0x2b4/+0x2b8/+0x2bc] and the side array rooted at [site+0x2e4]"
|
|
.to_string(),
|
|
"0x0040d450 owner-company-aware local scorer writing directly into [site+0x310] via 0x00455810/0x00455800/0x0044ad60 and 0x00436590 ids 0x66/0x68"
|
|
.to_string(),
|
|
"0x0040fb70 small wrapper passing the subject vtable slot +0x80 result plus owner-present flag into 0x00412560"
|
|
.to_string(),
|
|
"0x00412560 shared candidate/admissibility gate over 0x0062ba8c candidate rows and world date/flags before the tri-lane scoring loop"
|
|
.to_string(),
|
|
"0x00410b30..0x004118f4 broader candidate-processing loop writing [site+0x310/+0x338/+0x360] after 0x00412560"
|
|
.to_string(),
|
|
"0x00401633 acquisition-adjacent 0x0040d540 caller immediately feeding company stat-family 0x2329/0x0d"
|
|
.to_string(),
|
|
"0x0044b81a owner-company-aware 0x0040d540 caller reaching 0x0040cb70 and 0x00436590 news/event id 0x65"
|
|
.to_string(),
|
|
"0x004b4052 / 0x004b46ec collection-wide 0x0040fb70 callers iterating 0x0062b26c candidate rows"
|
|
.to_string(),
|
|
"0x004b70f5 / 0x004b7979 broader 0x0040d540 callers routing through 0x004337a0 and downstream 0x00540120 / 0x00518140 state consumers"
|
|
.to_string(),
|
|
"0x0040d360 placed-structure subtype-4 predicate over [candidate+0x32]"
|
|
.to_string(),
|
|
"0x0040d540 weighted region-to-company proximity scorer with pending-bonus context"
|
|
.to_string(),
|
|
"0x0040f6d0 / 0x00481390 / 0x00480210 subtype-1 constructor family seeding linked record own id, anchor-site id, and linked peer id"
|
|
.to_string(),
|
|
"0x0040d210 owner-side placed-structure resolver from [site+0x276] through 0x0062be10"
|
|
.to_string(),
|
|
"0x0041f7e0 / 0x0041f810 / 0x0041f850 peer-chain helpers grounding [site+0x2a4] as the record's own site id"
|
|
.to_string(),
|
|
"0x00481390 / 0x00480210 subtype-1 linked-site allocation and constructor".to_string(),
|
|
"0x00444690 late world bring-up caller of 0x004133b0 placed-structure local-runtime replay"
|
|
.to_string(),
|
|
"0x004133b0 placed-structure local-runtime replay owner draining queued site ids through 0x0040e450 and sweeping live sites through 0x0040ee10".to_string(),
|
|
"0x0040e360..0x0040edf6 broader replay continuation consuming [site+0x2a8/+0x2a4/+0x276] around 0x0040d230 / 0x0040d1f0 / 0x00480710 / 0x00426b10 / 0x00455860"
|
|
.to_string(),
|
|
"0x0040e450 queued site-id cloned local-runtime replay helper".to_string(),
|
|
"0x0040ee10 live-site position/scalar refresh helper reaching 0x0040edf6 -> 0x00480710 and 0x0040e360".to_string(),
|
|
"0x00480710 linked-site runtime side-buffer and route-entry-anchor refresh owner"
|
|
.to_string(),
|
|
"0x0042bbf0 / 0x0042bbb0 world-cell owner-chain refresh over [cell+0xd4]".to_string(),
|
|
"0x0042c9f0 / 0x0042c9a0 world-cell linked-site-chain refresh over [cell+0xd6]"
|
|
.to_string(),
|
|
"0x0040df27 / 0x0040e00a / 0x0040edf6 concrete linked-site side-refresh callers of 0x00480710"
|
|
.to_string(),
|
|
"0x004160aa non-bring-up runtime caller of 0x0040ee10".to_string(),
|
|
"0x0048abc0 / 0x00493cf0 route-entry-anchor rebind and synthesis strip".to_string(),
|
|
"0x0047dda0 linked-peer route-entry-anchor validator".to_string(),
|
|
"0x00420030 / 0x00420280 peer-site boolean/selector pair over the placed-structure collection".to_string(),
|
|
"0x0047efe0 placed_structure_query_linked_company_id returning owner company id from [site+0x276]".to_string(),
|
|
"0x004269b0 acquisition commit owner resolving one site id through 0x0062b26c and then mutating [site+0x276]/[site+0x27a] on the chosen live row".to_string(),
|
|
"0x00426dce..0x00426ea1 bulk placed-structure owner-company reassignment over 0x0062b26c for non-subtype-4 rows matching the current company".to_string(),
|
|
"0x00430040..0x004300d6 filtered placed-structure owner-company overwrite over site classes 0x09/0x0b/0x0c".to_string(),
|
|
"0x00431b20 grouped opcode dispatcher over 0x0061039c routing opcodes 0x04..0x07 to 0x00430040, 0x08/0x10..0x13 to 0x00426d60, and 0x0d/0x16 to 0x0042fc90".to_string(),
|
|
"0x0042fc90 live placed-structure mutator iterating 0x0062b26c through 0x0040c990, optional owner-company match [site+0x276], and row vtable slot +0x70".to_string(),
|
|
"0x0047fd50 linked-peer candidate-class gate returning true only for class-byte values 0/1/2 at [candidate+0x8c]".to_string(),
|
|
"0x004131f0 / 0x00412fb0 / 0x004120b0 / 0x00412ab0 aux-candidate load and stem-policy chain owning subtype byte [candidate+0x32]".to_string(),
|
|
"0x004134d0 direct allocator calling constructor 0x0040f6d0 for new placed-structure rows".to_string(),
|
|
"0x0040f6d0 create-side initializer seeding [site+0x2a4], [site+0x276], [site+0x3d4/+0x3d5], and cleared local caches".to_string(),
|
|
"0x0040dc40 station-detail linked-site mutation validator/apply path consuming [site+0x276], candidate field [candidate+0x22], and company stat-family 0x2329/0x0d"
|
|
.to_string(),
|
|
"0x00417840 / 0x004197e0 / 0x004142c0 / 0x004142d0 projected-cell validation and compact-grid replay strip ahead of the linked-site mutation"
|
|
.to_string(),
|
|
"0x0040d1f0 / 0x00480710 / 0x0045b160 / 0x0045b9b0 / 0x00418be0 / 0x0040cd70 linked-site mutation-side side-state rebuild strip"
|
|
.to_string(),
|
|
"0x00402cb0 / 0x00403ed5 / 0x0040446b city-connection direct-placement commit family"
|
|
.to_string(),
|
|
"0x00403ef3 / 0x00404489 create-side callers of shared finalize helper 0x0040ef10".to_string(),
|
|
"0x0046f073 / 0x004707ff data-driven loader callers of shared finalize helper 0x0040ef10".to_string(),
|
|
"0x0046f073 / 0x004707ff tuple field [+0x0c] feeding 0x0040ef10 arg3 and then [site+0x276] at 0x0040f5d4".to_string(),
|
|
"0x004706b0 multiplayer transport selector-0x13 body re-entering 0x004197e0 / 0x004134d0 / 0x0040eba0 / 0x0052eb90 / 0x0040ef10 before 0x004707ff".to_string(),
|
|
"0x00472b40 multiplayer transport selector-0x72 counted live-world apply path whose inner builders 0x00472bef / 0x00472d03 reach 0x004134d0 from counted transport records".to_string(),
|
|
"0x00422bb4 direct non-tuple allocator caller pushing one 0x0062b2fc record plus local args and literal flags 1/0 into 0x004134d0, then returning the created row id through an out-param".to_string(),
|
|
"0x00508fd1 / 0x005098eb live controller family caching a created site id in [this+0x7c], re-entering 0x0040eba0 with immediate coords, and later calling 0x0040ef10 with arg3 forced to zero".to_string(),
|
|
"0x00473c20 live queued-site refresh draining scratch band 0x006ce808..0x006ce988 and re-entering 0x0040eba0 at 0x00473c98 before clearing each queued id slot".to_string(),
|
|
"0x0042128d broad zero-init in the 0x00421430 constructor neighborhood clearing [site+0x276] with the surrounding site reset band".to_string(),
|
|
"0x00422305 live score/category publisher writing [site+0x276] before event 0x7 dispatch, not ordinary restore".to_string(),
|
|
"0x004269c9 / 0x00426a2a acquisition commit and clear helpers mutating [site+0x276]/[site+0x27a] on chosen live rows".to_string(),
|
|
"0x004282a9 / 0x004300d6 bulk owner-transfer writes over existing live placed-structure rows".to_string(),
|
|
"0x00413440 paired tagged 0x36b1/0x36b2/0x36b3 collection serializer dispatching each live record through vtable slot +0x44".to_string(),
|
|
"0x004134d0 / 0x0040ef10 shared placed-structure allocator and finalize-or-rebuild lane for newly created or tuple-loaded site rows"
|
|
.to_string(),
|
|
"0x00481430 / 0x0047d8e0 dynamic side-buffer stream-load owner repopulating route-entry lists, three byte arrays, five proximity buckets, and trailing scratch band"
|
|
.to_string(),
|
|
"0x0040c9a0 deferred additive accumulator/reset helper folding tri-lane [site+0x310/+0x338/+0x360] into [site+0x2b4/+0x2b8/+0x2bc] and mirroring the nine-dword side array rooted at [site+0x2e4]"
|
|
.to_string(),
|
|
"0x0040a3a1..0x0040a4d3 broad live-collection maintenance sweep calling 0x0040c9a0 once per live placed structure after sibling sweeps over companies, source records, and peer sites".to_string(),
|
|
"0x0040fcc0..0x0040fe28 and 0x00422c62..0x00422d3c weighted scoring families consuming 0x0040cac0 without grounding a tri-lane producer".to_string(),
|
|
"0x005c8c50 +0x40 stream-load callback 0x0040ce60 canonicalizing the site stem and re-entering 0x0040cd70 / 0x0045c150 for restored rows"
|
|
.to_string(),
|
|
"0x0052edf0 generic base constructor seeding [this+0x04] from caller arg 1".to_string(),
|
|
"0x00455b70 placed-structure specialization constructor feeding 0x0052edf0 arg 3 as the primary selector and arg 1 as fallback"
|
|
.to_string(),
|
|
"0x00455c62 concrete placed-structure specialization caller of 0x0052edf0".to_string(),
|
|
"0x00456100 local wrapper duplicating its first incoming arg across the 0x00455b70 selector/fallback bundle"
|
|
.to_string(),
|
|
"0x00456072 fixed 0x55f2 callback forwarding three local dwords plus unit scalars into 0x00455b70"
|
|
.to_string(),
|
|
"0x0045c36e / 0x0045da65 / 0x0045e0fc dense 0x00456100 caller family over stack-backed buffers and default scalar lanes"
|
|
.to_string(),
|
|
"0x0045c36e feeds 0x00456100 selector arg 1 from [owner+0x23e], 0x0045da65 feeds zero, and 0x0045e0fc feeds [ebp+0x08]"
|
|
.to_string(),
|
|
"0x0045c150 save-backed loader for [owner+0x23e/+0x242] via tagged payload 0x5dc1 ahead of 0x0045c310"
|
|
.to_string(),
|
|
"0x0040ceab / 0x0040d1a1 local linked-site helper strip calling 0x0045c150 / 0x0045c310 directly"
|
|
.to_string(),
|
|
"0x00485819 typed placed-structure caller of 0x0052edf0 via 0x530640-style argument bundle"
|
|
.to_string(),
|
|
"0x00490a79 chooser-side caller of 0x00455b70 with literal selector 0x005cfd74 and fallback seed 0x005c87a8"
|
|
.to_string(),
|
|
"0x00406050 city-connection bonus/news sibling owner".to_string(),
|
|
"0x00409950 linked-transit roster sibling owner".to_string(),
|
|
];
|
|
let next_owner_questions = vec![
|
|
"Which persisted placed-structure and city-or-region linkage fields are still missing for a shellless 0x004014b0 implementation once the peer-site restore subset, company route-anchor tuple, [site+0x276] live owner meaning, and candidate subtype owner strip are accepted as grounded?".to_string(),
|
|
"How much of the linked-peer refresh path is strictly post-load versus recurring runtime maintenance now that 0x004133b0 reaches 0x0040ee10 -> 0x0040edf6 -> 0x00480710 during bring-up and 0x004160aa also re-enters 0x0040ee10 later?".to_string(),
|
|
"Which save-native or replay seam repopulates placed-structure owner-company field [site+0x276] for already-restored rows once [site+0x2a4] is treated as reconstructible from collection identity, the replay strip is ruled down, and the 0x00431b20 grouped opcode dispatcher is treated as a live application owner rather than a restore seam?".to_string(),
|
|
"Which persisted inputs and exact shellless formulas feed the grounded tri-lane live scorer family 0x0040d450 / 0x00410b30..0x004118f4 above 0x00412560 before 0x0040c9a0 folds the results into [site+0x2b4/+0x2b8/+0x2bc]?".to_string(),
|
|
"Which infrastructure consumer above the grounded 0x38a5 seam actually drives the linked-transit branch that 0x00409950 follows?".to_string(),
|
|
];
|
|
let linked_transit_shellless_readiness_status =
|
|
"timed_cache_and_train_side_followons_grounded_site_cache_input_owners_missing".to_string();
|
|
let linked_transit_minimum_persisted_identity_inputs = vec![
|
|
"save-backed company identity, current company id, and linked-transit latch [company+0x0d56] selecting one per-company cache cell root beneath [site+0x5bd][company_id]".to_string(),
|
|
"save-backed linked-transit route-anchor tuple [company+0x0d35] and fallback count lanes [company+0x7664/+0x7668/+0x766c] feeding the reachable-site strip above 0x00401860 / 0x004801a0".to_string(),
|
|
"save-backed placed-structure owner and class identity lanes [site+0x276] and [site+0x04] consumed by 0x0047efe0 / 0x0047fd50 before any linked-transit site is marked eligible".to_string(),
|
|
"save-backed placed-structure and peer identity lanes [site+0x2a4], [site+0x2a8], and [peer+0x04/+0x08] giving the live site and linked-peer ids that the cache rebuilds query".to_string(),
|
|
"save-backed world calendar lanes [world+0x15] and [world+0x0d] driving refresh cadence, age stamping, and the year-banded policy table used by 0x00408380".to_string(),
|
|
];
|
|
let linked_transit_live_rebuilt_cache_lanes = vec![
|
|
"0x004093d0 stamps [company+0x0d3e], clears each selected [site+0x5bd][company_id] cell, frees and reallocates its peer table at +0x06, repopulates +0x02/+0x06/+0x0a, and marks bytes +0x00/+0x01 from the live site filter".to_string(),
|
|
"0x004093d0 fills each 0x0d-stride peer row from 0x004a6630, so peer-table dword +0x05 step count and float +0x09 normalized continuity share are rebuilt scratch from live route-entry tracker results".to_string(),
|
|
"0x00407bd0 clears [site+0x0e/+0x12/+0x16] before folding the rebuilt peer rows plus candidate tables back into weighted/raw/final per-site score lanes".to_string(),
|
|
"0x00481910 and 0x004819b0 rebuild the local occupancy/count lane [site+0x5c1] from current-site-id resolver 0x004a9340 rather than from any serialized cache blob".to_string(),
|
|
"0x004aee2b rewrites [site+0x5c5] from world counter [world+0x15], making the chooser age bonus a live rebuilt lane rather than persisted cache state".to_string(),
|
|
];
|
|
let linked_transit_runtime_backed_input_families = vec![
|
|
"company linked-transit route-anchor tuple [company+0x0d35] and fallback count lanes [company+0x7664/+0x7668/+0x766c] through 0x00401860".to_string(),
|
|
"company linked-transit peer-cache refresh absolute counter [company+0x0d3e] driving the shorter 0x00409720 -> 0x004093d0 cadence".to_string(),
|
|
"company linked-transit autoroute site-score refresh absolute counter [company+0x0d3a] driving the longer 0x00409720 -> 0x00407bd0 cadence".to_string(),
|
|
"company linked-transit latch [company+0x0d56] consumed by 0x00409950 and the annual-finance debt lane".to_string(),
|
|
"active-company refresh owner 0x00429c10 walks the live company roster and re-enters 0x004093d0 for each active company when linked-transit site-peer caches need a broader rebuild".to_string(),
|
|
"placed-structure-side company cache root [site+0x5bd] allocated by 0x00407780 as a 0x20-entry pointer table of 0x1a-byte per-company cache cells and freed by 0x004077e0".to_string(),
|
|
"placed-structure replay strip 0x00444690 -> 0x004133b0 -> 0x0040ee10 -> 0x0040edf6 -> 0x00480710 already republishes linked-peer ids, route-entry anchors, and world-cell owner chains before later recurring call 0x004160aa re-enters 0x0040ee10".to_string(),
|
|
"placed-structure-side per-company cache cells addressed through [site+0x5bd][company_id] and peer rows filled by 0x004a6630".to_string(),
|
|
"per-company cache-cell layout is bounded too: bytes +0x00/+0x01 gate participation, dword +0x02 is peer-row count, dword +0x06 is peer-row pointer, dword +0x0a is the peer-cache refresh stamp, and floats +0x0e/+0x12/+0x16 are the weighted/raw/final linked-transit score lanes".to_string(),
|
|
"linked-transit peer-table row lanes +0x05 step count and +0x09 normalized continuity share under 0x004093d0".to_string(),
|
|
"linked-transit site-score cache lanes [site+0x0e/+0x12/+0x16] rebuilt by 0x00407bd0".to_string(),
|
|
"linked-transit local site occupancy/count lane [site+0x5c1] reset by 0x00481910, repopulated from current-site-id resolver 0x004a9340, adjusted by 0x004819b0, and consumed by 0x00408280 as a divisor/penalty lane".to_string(),
|
|
"linked-transit local site age lane [site+0x5c5] stamped from world counter [world+0x15] at 0x004aee2b and consumed by 0x00408280 as the zero-occupancy age bonus ladder".to_string(),
|
|
"structure candidate table root 0x0062ba8c is world-load owned by 0x0041f4e0 -> 0x0041ede0 -> 0x0041e970 before 0x00407bd0 reuses candidate-local bands".to_string(),
|
|
"route-entry tracker compatibility and endpoint-fallback chooser 0x004a6360 / 0x004a6630 sit under owner-notify refresh 0x00494fb0, so the peer-table metric source is already bounded above the linked-transit caches".to_string(),
|
|
"linked-transit aggregate roster-pressure helper 0x00408f70 consuming raw site-score lane [site+0x12]".to_string(),
|
|
"linked-transit ranked-site chooser 0x00408280 and staged autoroute entry builder 0x00408380 above the rebuilt site caches".to_string(),
|
|
"linked-transit train-side autoroute append / rotate strip 0x00409770 plus add-train owner 0x00409830 beneath roster balancer 0x00409950".to_string(),
|
|
];
|
|
let linked_transit_remaining_owner_gaps = vec![
|
|
"which earlier restore or service owner feeds [site+0x276] and the live linked-peer rows before replay continuation 0x0040e360..0x0040edf6, now that direct inspection shows the 0x0040ea96..0x0040eb65 owner-company branch only consumes [site+0x276] and subtype-4 follow-on 0x0040eba0 already republishes [site+0x2a4] into the world-cell chains before 0x004093d0 / 0x00407bd0 rebuild their scratch cache lanes".to_string(),
|
|
"how much of the live placed-structure collection 0x006cec20 and linked-peer replay strip still has to run shelllessly beside already-grounded candidate-table owner 0x0041f4e0 / 0x0041ede0 and route-entry tracker owner 0x00494fb0 / 0x004a6360 before 0x00408280 / 0x00408380 / 0x00409770 become trustworthy".to_string(),
|
|
];
|
|
|
|
let companies = analysis
|
|
.company_entries
|
|
.iter()
|
|
.map(|entry| {
|
|
let mut branches = Vec::new();
|
|
branches.push(build_service_trace_branch_status(
|
|
"route_preference_override",
|
|
if entry.preferred_locomotive_engine_type_raw_u8 == 2 {
|
|
"grounded_electric_override_candidate"
|
|
} else {
|
|
"grounded_non_electric_or_inactive_override_candidate"
|
|
},
|
|
&[
|
|
"company periodic side-latch trio",
|
|
"world route-preference byte",
|
|
"preferred locomotive engine-type lane",
|
|
],
|
|
&[],
|
|
&[
|
|
"0x004019e0 periodic company outer owner",
|
|
"0x004078a0 preferred-locomotive chooser",
|
|
"0x0041d550 locomotive-era and engine-type approval gate",
|
|
],
|
|
&[
|
|
"This probe keeps the outer owner at the save-owned input level; the concrete runtime reader/apply/restore seam is already grounded separately.",
|
|
],
|
|
));
|
|
branches.push(build_service_trace_branch_status(
|
|
"annual_finance_policy",
|
|
"runnable_from_grounded_owner_state",
|
|
&[
|
|
"company market/cache owner state",
|
|
"periodic side-latches",
|
|
"world issue/timing owner state",
|
|
"derived annual-finance readers",
|
|
],
|
|
&[],
|
|
&[
|
|
"0x004019e0 periodic company outer owner",
|
|
"0x00401c50 annual finance-policy owner",
|
|
],
|
|
&[
|
|
"The shellless annual-finance helper is already rehosted on top of runtime-owned state.",
|
|
],
|
|
));
|
|
branches.push(build_service_trace_branch_status(
|
|
"city_connection_announcement",
|
|
"blocked_missing_region_and_infrastructure_asset_owner_seams",
|
|
&[
|
|
"company periodic side-latches",
|
|
"route-preference override seam",
|
|
"annual-finance sequencing owner",
|
|
],
|
|
&[
|
|
"region pending/completion/one-shot/severity lanes",
|
|
"stable region id or class discriminator",
|
|
"placed-structure or infrastructure-asset consumer mapping",
|
|
],
|
|
&[
|
|
"0x004019e0 periodic company outer owner",
|
|
"0x00406050 city-connection bonus/news owner",
|
|
"0x00420030 / 0x00420280 city-connection peer probes",
|
|
"0x0047efe0 placed-structure linked-company resolver",
|
|
],
|
|
&[
|
|
"Current atlas evidence places this branch above both the region pending-bonus lane and infrastructure/placed-structure consumers.",
|
|
],
|
|
));
|
|
branches.push(build_service_trace_branch_status(
|
|
"linked_transit_roster_maintenance",
|
|
"blocked_missing_site_cache_input_owner_mapping",
|
|
&[
|
|
"company linked-transit latch",
|
|
"company linked-transit route-anchor tuple",
|
|
"company linked-transit peer-cache refresh absolute counter [company+0x0d3e]",
|
|
"company linked-transit autoroute-score refresh absolute counter [company+0x0d3a]",
|
|
"route-preference override seam",
|
|
],
|
|
&[
|
|
"placed-structure site-cache input owners beneath 0x004093d0 / 0x00407bd0",
|
|
"persisted site-side inputs behind 0x00408280 / 0x00408380",
|
|
],
|
|
&[
|
|
"0x004019e0 periodic company outer owner",
|
|
"0x00409720 timed linked-transit cache-service wrapper",
|
|
"0x004093d0 linked-transit site-peer cache rebuild",
|
|
"0x00407bd0 linked-transit autoroute site-score cache rebuild",
|
|
"0x00408280 linked-transit ranked-site chooser",
|
|
"0x00408380 linked-transit staged autoroute-entry builder",
|
|
"0x00408f70 linked-transit aggregate site-score pressure helper",
|
|
"0x00409770 linked-transit autoroute append/rotate owner",
|
|
"0x00409830 linked-transit add-train/news owner",
|
|
"0x00409950 linked-transit train-roster balancer",
|
|
],
|
|
&[
|
|
"The save side now grounds the timed cache-owner seams, ranked-site chooser, staged route builder, and train-side follow-ons; the remaining blocker is the placed-structure site-cache input ownership beneath those bounded consumers, not the train-side strip itself.",
|
|
],
|
|
));
|
|
branches.push(build_service_trace_branch_status(
|
|
"industry_acquisition_side_branch",
|
|
"blocked_missing_near-city_owner_mapping",
|
|
&[
|
|
"periodic service outer owner",
|
|
"annual-finance ordering",
|
|
],
|
|
&[
|
|
"near-city industry candidate owner seam",
|
|
"city or region peer linkage",
|
|
],
|
|
&[
|
|
"0x004019e0 periodic company outer owner",
|
|
"0x004014b0 near-city industry acquisition and news owner",
|
|
"0x004010f0 near-city acquisition region scorer",
|
|
"0x00405920 same-company industry proximity aggregator",
|
|
"0x0041f6e0 center-cell token gate",
|
|
"0x0042b2d0 packed-u16 contains-key predicate",
|
|
"0x0047de00 linked-region resolver feeding 0x0040c990",
|
|
"0x004801a0 linked-transit route-anchor reachability gate",
|
|
"0x00425b90 pending-bonus/company-state gate",
|
|
"0x0040d360 region type/class filter",
|
|
"0x0040d540 weighted region-to-company proximity scorer",
|
|
"0x0040f6d0 subtype-1 placed-structure constructor",
|
|
"0x00481390 / 0x00480210 subtype-1 linked-site allocation and constructor",
|
|
"0x00444690 late world bring-up caller of 0x004133b0",
|
|
"0x004133b0 placed-structure local-runtime replay owner",
|
|
"0x0040e450 queued site-id cloned local-runtime replay helper",
|
|
"0x0040ee10 live-site position/scalar refresh helper",
|
|
"0x00480710 linked-site runtime side-buffer and route-entry-anchor refresh owner",
|
|
"0x0040df27 / 0x0040e00a / 0x0040edf6 linked-site side-refresh callers",
|
|
"0x004160aa non-bring-up runtime caller of 0x0040ee10",
|
|
"0x0048abc0 / 0x00493cf0 route-entry-anchor rebind and synthesis strip",
|
|
"0x0047dda0 linked-peer route-entry-anchor validator",
|
|
"0x00420030 / 0x00420280 peer-site boolean/selector pair",
|
|
"0x00406050 city-connection bonus/news sibling owner",
|
|
],
|
|
&[
|
|
"Direct disassembly now shows this branch scanning the live placed-structure collection at 0x0062b26c for the best current acquisition target, rejecting sites whose owner field [site+0x276] is already nonzero, reusing the center-cell token gate 0x0041f6e0 -> 0x0042b2d0, reusing the linked-region status branch 0x0047de00 -> 0x0040c990, checking candidate reachability through 0x004801a0, consulting the placed-structure peer-site boolean/selector pair 0x00420030 / 0x00420280 over 0x006cec20, scoring candidate sites against company proximity and age through 0x0040d540 and 0x0040cac0, and then committing the chosen site through 0x004269b0. The peer-site selector seam itself is now grounded through the local helper strip: 0x0040cd70 seeds [site+0x3cc/+0x3d0] from 0x62b2fc / 0x62b268, 0x0040ceab and 0x0040d1a1 reach the save-backed 0x0045c150 / 0x0045c310 owner directly, that owner fills [owner+0x23e/+0x242] from tagged payload 0x5dc1, and 0x0045c36e then feeds [owner+0x23e] through 0x00456100 -> 0x00455b70 -> 0x0052edf0 into the live backing-record selector [site+0x04]. The cached tri-lane is no longer a restore-only mystery either: 0x0040d450 and 0x00410b30..0x004118f4 now bound the live writer family above the shared 0x00412560 candidate/admissibility gate, 0x0040fb70 is the small wrapper into that gate, and direct callers now separate acquisition-adjacent 0x0040d540 users like 0x00401633 / 0x0044b81a from broader sibling sweeps such as 0x004b4052 / 0x004b46ec / 0x004b70f5 / 0x004b7979. The remaining linked-site field work is now about which persisted site/peer lanes are actually sufficient for shellless acquisition and city-connection behavior, not about who owns [site+0x04] or whether the tri-lane has live producers.",
|
|
],
|
|
));
|
|
SmpPeriodicCompanyServiceTraceEntry {
|
|
company_id: entry.company_id,
|
|
name: entry.name.clone(),
|
|
active: entry.active,
|
|
linked_chairman_profile_id: entry.linked_chairman_profile_id,
|
|
preferred_locomotive_engine_type_raw_u8: entry
|
|
.preferred_locomotive_engine_type_raw_u8,
|
|
city_connection_latch: entry.city_connection_latch,
|
|
linked_transit_latch: entry.linked_transit_latch,
|
|
linked_transit_autoroute_site_score_cache_refresh_absolute_counter: entry
|
|
.linked_transit_autoroute_site_score_cache_refresh_absolute_counter,
|
|
linked_transit_site_peer_cache_refresh_absolute_counter: entry
|
|
.linked_transit_site_peer_cache_refresh_absolute_counter,
|
|
branches,
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let mut notes = Vec::new();
|
|
notes.push(
|
|
"Periodic company service trace is intentionally an outer-owner probe: it reports save-owned branch inputs and blocker seams without serializing the full projected runtime reader state.".to_string(),
|
|
);
|
|
notes.push(
|
|
"Direct disassembly now narrows the acquisition-side sibling substantially: 0x004014b0 gates on the periodic outer owner, then scans the live placed-structure collection at 0x0062b26c, rejects sites whose owner field [site+0x276] is already nonzero, filters candidates through the subtype-4 predicate 0x0040d360, scores surviving sites against company linkage/age/proximity through 0x0040d540 and 0x0040cac0, and then commits the chosen site through 0x004269b0 before the shared news lane 0x004554e0 formats the headline.".to_string(),
|
|
);
|
|
notes.push(
|
|
"The shellless acquisition frontier is narrower now too: the peer-site selector/linked-peer seam is grounded enough to plan around, the company/chairman side now includes the save-native route-anchor tuple used by 0x00401860, the live owner-company meaning of [site+0x276] is grounded through 0x0047efe0 / 0x0040d210, the self-id meaning of [site+0x2a4] is grounded through 0x0041f7e0 / 0x0041f810 / 0x0041f850, constructor-side 0x00480210 already seeds that self-id lane for new linked-site rows, 0x004269b0 resolves the chosen site id back through 0x0062b26c before mutating [site+0x276], the cached tri-lane now has grounded live producers at 0x0040d450 and 0x00410b30..0x004118f4 above 0x00412560, and the subtype owner strip is bounded under the aux-candidate load chain 0x004131f0 -> 0x00412fb0 -> 0x004120b0 -> 0x00412ab0. The remaining blocker is the placed-structure-side restore or replay ownership for [site+0x276], the persisted inputs and exact shellless semantics of the live cached tri-lane scorer family, plus the projection from [site+0x04] back into the loaded candidate subtype row.".to_string(),
|
|
);
|
|
notes.push(
|
|
"The acquisition-side consumer family is tighter now too. The checked-in station-detail action path 0x0040dc40 already consumes live owner company [site+0x276], candidate field [candidate+0x22], company stat-family 0x2329/0x0d, projected-cell validation 0x00417840 -> 0x004197e0, and compact-grid replay 0x004142c0/0x004142d0 before it commits the linked-site mutation through 0x0040d1f0 -> 0x00480710 -> 0x0045b160 / 0x0045b9b0 / 0x00418be0 / 0x0040cd70. That means these lanes are already grounded as live preconditions and mutation-side rebuild inputs, even though the save or replay owner that populates them for shellless acquisition is still the open question.".to_string(),
|
|
);
|
|
notes.push(
|
|
"The create-side family is grounded separately too. City-connection direct placement already reaches the shared constructor/finalize strip 0x00402cb0 -> 0x00403ed5/0x0040446b -> 0x004134d0 -> 0x0040ef10, and the direct writer census now shows [site+0x276] writes clustering under create-side, acquisition-side, and bulk control-transfer families rather than under the known replay strip. So the remaining shellless gap is no longer 'how are new placed structures finalized?', '[site+0x2a4] mystery', or 'does the tri-lane even have live writers?'; it is specifically how restored existing rows regain the owner-company lane and which persisted inputs feed the grounded tri-lane scorer family before acquisition-style consumers run.".to_string(),
|
|
);
|
|
notes.push(
|
|
"The tri-lane restore side is narrower now too. The checked-in dynamic side-buffer load owner 0x00481430 -> 0x0047d8e0 repopulates the route-entry list, three per-site byte arrays, five proximity buckets, and the trailing scratch band from stream, so that seam is no longer a plausible hidden owner for [site+0x310/+0x338/+0x360].".to_string(),
|
|
);
|
|
notes.push(
|
|
"Direct local binary inspection now also gives the tri-lane a concrete live runtime role: 0x0040c9a0 folds [site+0x310/+0x338/+0x360] into the local scalar band [site+0x2b4/+0x2b8/+0x2bc], mirrors the nine-dword side array rooted at [site+0x2e4], and then clears the tri-lane. Caller census keeps that accumulator role narrow too: 0x0040c9a0 only appears under the broad live-collection sweep 0x0040a3a1..0x0040a4d3, while 0x0040cac0 stays under weighted scoring/evaluation families such as 0x0040fcc0..0x0040fe28 and 0x00422c62..0x00422d3c. The direct writer strip is grounded too: 0x0040d450 writes [site+0x310] through owner-company-aware scoring, and the broader 0x00410b30..0x004118f4 candidate-processing loop writes [site+0x310/+0x338/+0x360] after gating rows through 0x00412560.".to_string(),
|
|
);
|
|
notes.push(
|
|
"Cross-save compare now tightens the save-native side too: `compare_save_region_fixed_row_run_candidates` keys the pre-region-header fixed-row bands by lane-shape fingerprint because grounded saves do not keep one stable top rows_offset, but they do retain shared shape-family matches. The tri-lane-adjacent save seam should therefore be treated as a stable row-family problem, not a fixed-offset problem."
|
|
.to_string(),
|
|
);
|
|
if let Some(best_shape_family) =
|
|
near_city_acquisition_tri_lane_save_shape_family_candidates.first()
|
|
{
|
|
notes.push(format!(
|
|
"The periodic-company trace now also carries the current top tri-lane-adjacent save row family: rank {} shape-family {} at rows {} with stride {} and probable density lane {:?}. That keeps the persisted side on a concrete save-native candidate family while the live tri-lane writers stay grounded separately.",
|
|
best_shape_family.rank,
|
|
best_shape_family.shape_family_signature,
|
|
best_shape_family.rows_offset_hex,
|
|
best_shape_family.row_stride_hex,
|
|
best_shape_family
|
|
.best_probable_density_lane_relative_offset_hex
|
|
.as_deref()
|
|
));
|
|
}
|
|
notes.push(
|
|
"The periodic-company trace now also surfaces the strongest non-transport persisted source candidate for [site+0x276]: the ordinary loaded runtime-effect lane 0x00444d92 -> 0x00432f40(kind 8) -> 0x004323a0 -> 0x00431b20 above the non-direct 0x4e99/0x4e9a/0x4e9b bundle. That branch is no longer just a blocker note; the remaining question is the tighter control-lane mapping from loaded rows into trigger kind 8 and then into the placed-structure mutation opcodes."
|
|
.to_string(),
|
|
);
|
|
notes.push(
|
|
"Installed-map cluster counts now sharpen that candidate lane too: the current rt3_105/maps corpus has 41 bundled maps, 38 of them with dispatch-strip rows, and 318 dispatch-strip records total, all still on the non-direct compact family with null trigger kind. The add-building subset inside that corpus is narrower still at 10 grouped occurrences across 7 recovered descriptor keys (Barracks, Bauxite Mine, FarmGrain, Furniture Factory, Logging Camp, Port01, Warehouse05), again all with null trigger kind. So the open question is no longer whether ordinary loaded rows can reach the 0x00431b20 strip at scale; it is specifically how any of those rows acquire or bypass the missing trigger-kind control lane."
|
|
.to_string(),
|
|
);
|
|
notes.push(
|
|
"Direct local binary inspection now grounds the cached-candidate restore bridge too: the placed-structure stream-load owner 0x00413280 dispatches per-entry vtable slot +0x40 on the 0x005c8c50 specialization table, that slot resolves to 0x0040ce60, and 0x0040ce60 immediately re-enters 0x0040cd70 plus 0x0045c150. So the acquisition-side cached source/candidate bridge [site+0x3cc/+0x3d0] is no longer a generic restore mystery for stream-loaded rows; the remaining restored-row gaps are [site+0x276] and the deferred tri-lane.".to_string(),
|
|
);
|
|
notes.push(
|
|
"Direct disassembly now tightens the remaining placed-structure lanes too: 0x0040cac0 is only the raw tri-lane delta reader over [site+0x310/+0x338/+0x360]; 0x0040d360 is only the exact subtype test [candidate+0x32] == 4; and the direct writer strip now shows [site+0x2a4] staying replay/constructor-owned while [site+0x310/+0x338/+0x360] is service-produced by the grounded 0x0040d450 / 0x00410b30..0x004118f4 family above 0x00412560.".to_string(),
|
|
);
|
|
notes.push(
|
|
"That branch also reuses the same peer-site helper strip already bounded under the city-connection family: 0x0041f6e0 resolves the current center world-grid cell and checks one packed token through 0x0042b2d0, 0x0047de00 follows the linked region behind the candidate site into the status byte returned by 0x0040c990, and 0x004801a0 checks whether one candidate site is reachable from the cached company route anchor through 0x00401860 -> 0x0048e3c0.".to_string(),
|
|
);
|
|
notes.push(
|
|
"Direct disassembly now closes the collection identity too: 0x00420030 is the boolean peer gate and 0x00420280 is the first-match selector over the live placed-structure / peer-site collection 0x006cec20, combining 0x0042b2d0, the optional linked-company filter through 0x0047efe0, the station-or-transit gate 0x0047fd50, and the linked-region status branch 0x0047de00 -> 0x0040c990.".to_string(),
|
|
);
|
|
notes.push(
|
|
"The linked-transit sibling owner is tighter now too: timed wrapper 0x00409720 compares world counter [world+0x15] against the save-owned company refresh counters [company+0x0d3e] and [company+0x0d3a], reruns the shorter peer-cache rebuild 0x004093d0 on the tighter 0x7ff80 cadence, reruns the heavier autoroute score-cache rebuild 0x00407bd0 on the broader 0x31380 cadence, and then feeds the raw site-score total 0x00408f70 into roster balancer 0x00409950. That means the remaining blocker for shellless linked-transit maintenance is no longer the timing seam; it is the higher-layer placed-structure / infrastructure consumer mapping above those grounded cache owners.".to_string(),
|
|
);
|
|
notes.push(
|
|
"The chooser-and-train strip is tighter now too: 0x00408280 only picks among company-owned linked-transit sites whose cache cells are already live, ranking them off site-cache lane [site+0x16] with one extra boost from local cache words [site+0x5c1/+0x5c5], while 0x00408380 either reuses one explicit site id or falls back to 0x00408280 before building a staged 0x33-byte route entry. Above that, 0x00409770 services the caches through 0x00409720, asks 0x00408380 for one staged entry, and then either appends or rotates it in-place, while 0x00409830 repeats the same builder twice before publishing the add-train/news side. So the remaining blocker is not the train-side consumer strip; it is the placed-structure-side owner seam that makes those site-cache lanes and ready bits trustworthy for shellless runs.".to_string(),
|
|
);
|
|
notes.push(
|
|
"Those extra chooser-side local lanes are tighter now too: 0x00481910 first clears [site+0x5c1] across the live placed-structure collection and then repopulates it by resolving current site ids through 0x004a9340, while 0x004819b0 decrements one prior site's [site+0x5c1] and increments one new site's copy during later reassignment. 0x004a9340 itself is now bounded as a current-site-id resolver: it returns [this+0xa0] when present and otherwise falls back through 0x004b3360 before returning one dereferenced site id. The companion age lane [site+0x5c5] is stamped directly from world counter [world+0x15] at 0x004aee2b. 0x00408280 then uses [site+0x5c1] as an occupancy divisor/penalty and [site+0x5c5] as a zero-occupancy age bonus ladder, so those lanes are no longer anonymous chooser magic; they are grounded live cache inputs with bounded writer strips.".to_string(),
|
|
);
|
|
notes.push(
|
|
"The per-company cache root is grounded now too: 0x00407780 allocates [site+0x5bd] as a 0x20-entry pointer table and seeds each entry with one zeroed 0x1a-byte company cache cell, while 0x004077e0 frees that same root and any nested cell payloads. That means the remaining linked-transit gap is no longer cache-root allocation identity; it is which persisted site-side inputs are sufficient to repopulate those per-company cells and the downstream score lanes before the bounded chooser/train strip runs shelllessly.".to_string(),
|
|
);
|
|
notes.push(
|
|
"The per-company cache-cell layout is bounded now too: 0x004093d0 and 0x00407bd0 use bytes +0x00/+0x01 as the initial participation gates, dword +0x02 as the peer-row count, dword +0x06 as the peer-row pointer, dword +0x0a as the shorter peer-cache refresh stamp, and floats +0x0e/+0x12/+0x16 as the weighted/raw/final linked-transit score lanes. The candidate-table and route-entry-tracker owners are bounded above that too: 0x0062ba8c is constructed through 0x0041f4e0 -> 0x0041ede0 -> 0x0041e970, while 0x004a6360 / 0x004a6630 sit under owner-notify refresh 0x00494fb0. The remaining linked-transit gap is narrower again: subtype-4 follow-on 0x0040eba0 already republishes [site+0x2a4] through 0x004814c0 / 0x00481480 and world-cell chain helpers 0x0042c9f0 / 0x0042c9a0, and direct inspection of 0x0040ea96..0x0040eb65 shows that owner-company branch only consumes [site+0x276] rather than rehydrating it. That pushes the open question one level earlier to whichever restore or service owner feeds [site+0x276] and the live linked-peer rows before this replay continuation runs.".to_string(),
|
|
);
|
|
notes.push(
|
|
"Direct disassembly now closes the negative persistence side too: the direct 0x36b1 per-record callbacks serialize the shared base scalar triplets rooted at [this+0x206/+0x20a/+0x20e] plus the subordinate payload callback strip, while the 0x4a9d/0x4a3a/0x4a3b side-buffer owner only persists route-entry lists, three byte arrays, five proximity buckets, and the sampled-cell list. That means neither checked-in save owner seam currently persists the core peer-site identity fields [site+0x04], [site+0x2a8], or [peer+0x08] directly.".to_string(),
|
|
);
|
|
if !peer_site_selector_candidate_saved_payload_summaries.is_empty() {
|
|
notes.push(format!(
|
|
"The periodic-company trace now also carries a compact save-side summary of the tagged 0x5dc1 placed-structure profile payload/status pairs already parsed from the 0x36b1 triplet seam; dominant current pair is {} / {} x{}, dominant adjacent payload delta is {:?}, dominant post-secondary byte is {:?}, dominant fixed-policy trailing word is {:?}, and dominant pre-footer padding len is {:?}.",
|
|
peer_site_selector_candidate_saved_payload_summaries[0].profile_payload_dword_hex,
|
|
peer_site_selector_candidate_saved_payload_summaries[0].profile_status_kind,
|
|
peer_site_selector_candidate_saved_payload_summaries[0].count,
|
|
peer_site_selector_candidate_saved_payload_delta_summaries
|
|
.first()
|
|
.map(|entry| entry.delta_hex.as_str()),
|
|
peer_site_selector_candidate_saved_companion_byte_summaries
|
|
.first()
|
|
.map(|entry| entry.companion_byte_hex.as_str()),
|
|
peer_site_selector_candidate_saved_policy_trailing_word_summaries
|
|
.first()
|
|
.map(|entry| entry.policy_trailing_word_hex.as_str()),
|
|
peer_site_selector_candidate_saved_footer_padding_summaries
|
|
.first()
|
|
.map(|entry| entry.padding_len)
|
|
));
|
|
}
|
|
if !peer_site_selector_candidate_saved_nonzero_companion_name_pair_summaries.is_empty() {
|
|
let dominant_nonzero_companion =
|
|
&peer_site_selector_candidate_saved_nonzero_companion_name_pair_summaries[0];
|
|
notes.push(format!(
|
|
"The nonzero 0x5dc1 post-secondary byte residue is now narrower too: the trace exposes exact saved name pairs for nonzero-byte rows, and the current leading pair is {} / {} with byte {} x{}. The same atlas-backed owner strip already restores the repeated primary and secondary payload strings into [owner+0x23e] and [owner+0x242], so this byte should now be treated as a separate discriminator after the secondary string, not as the [owner+0x242] field itself. On grounded saves the exposed nonzero sample set is dominated by industry-like names rather than stations, maintenance, or residential rows, which makes the byte a stronger acquisition-side discriminator candidate than the monotone payload dword.",
|
|
dominant_nonzero_companion.primary_name,
|
|
dominant_nonzero_companion.secondary_name,
|
|
dominant_nonzero_companion.companion_byte_hex,
|
|
dominant_nonzero_companion.count
|
|
));
|
|
}
|
|
notes.push(
|
|
"Direct disassembly now also separates the narrower peer-class gate from that payload residue: 0x0047fd50 resolves the linked peer through [site+0x04], reads candidate class byte [candidate+0x8c], and returns true only for values 0/1/2 while rejecting 3/4 and above. That means the newly isolated post-secondary byte is not the already-grounded station-or-transit class gate itself; it remains a separate saved discriminator above the restored name-pair payload.".to_string(),
|
|
);
|
|
if let (Some(dominant_companion), Some(dominant_trailing_word)) = (
|
|
peer_site_selector_candidate_saved_companion_byte_summaries.first(),
|
|
peer_site_selector_candidate_saved_policy_trailing_word_summaries.first(),
|
|
) {
|
|
notes.push(format!(
|
|
"The same focused 0x36b1 triplet probe now also keeps the fixed-policy trailing word narrow at {} x{} while the profile side stays dominated by companion byte {} and payload/status pair {} / {}. Together that keeps the checked-in triplet seam looking like local structure/profile state rather than the missing acquisition owner-company lane [site+0x276] or cached tri-lane [site+0x310/+0x338/+0x360].",
|
|
dominant_trailing_word.policy_trailing_word_hex,
|
|
dominant_trailing_word.count,
|
|
dominant_companion.companion_byte_hex,
|
|
peer_site_selector_candidate_saved_payload_summaries[0].profile_payload_dword_hex,
|
|
peer_site_selector_candidate_saved_payload_summaries[0].profile_status_kind,
|
|
));
|
|
}
|
|
notes.push(
|
|
"The replay strip is tighter now too. 0x00444690 is the current late world bring-up caller of 0x004133b0, that outer owner drains queued site ids through 0x0040e450 and then sweeps every live placed structure through 0x0040ee10, and 0x0040ee10 itself reaches 0x0040edf6 -> 0x00480710 plus the later 0x0040e360 follow-on. A separate runtime path at 0x004160aa also re-enters 0x0040ee10 later. So [peer+0x08] replay is no longer the open question, and [site+0x04] is no longer an owner mystery either: the local linked-site helper strip seeds [site+0x3cc/+0x3d0] from 0x62b2fc / 0x62b268, reaches the save-backed 0x0045c150 / 0x0045c310 owner directly, that owner fills [owner+0x23e/+0x242] from tagged payload 0x5dc1, and 0x0045c36e then feeds [owner+0x23e] through 0x00456100 -> 0x00455b70 -> 0x0052edf0 into [site+0x04]. The remaining non-hook target is now the smaller shellless-simulation question: which subset of those persisted site/peer fields is actually sufficient to run 0x004014b0 and its city-connection sibling without shell state.".to_string(),
|
|
);
|
|
notes.push(
|
|
"The same persisted selector seam is broader than just the two strings. Atlas-backed recovery now bounds 0x0040c980 -> 0x0045b560 as the derived serializer over [site+0x23e/+0x242/+0x246/+0x24e/+0x252], so the remaining restore-owner search should treat that 0x5dc1/0x5dc2 selector/child/runtime bundle as one persisted field family rather than only [site+0x23e/+0x242]."
|
|
.to_string(),
|
|
);
|
|
notes.push(
|
|
"The loader-side counterpart now narrows the shellless minimum persisted subset too. 0x0045c150 restores [owner+0x23e] and [owner+0x242], clears the transient roots, and then hands off to 0x0045c310 / 0x0045b5f0 / 0x0045b6f0 to rebuild the primary child handle and larger ambient/animation/light/random-sound family. That means the broader 0x5dc1/0x5dc2 bundle should be treated as one persisted owner seam, but current shellless planning can keep the minimum identity subset at the cached ids [site+0x3cc/+0x3d0], the restored name-pair [owner+0x23e/+0x242], and the post-secondary discriminator byte while the child/runtime follow-ons stay on the rebuild side."
|
|
.to_string(),
|
|
);
|
|
|
|
SmpPeriodicCompanyServiceTraceReport {
|
|
profile_family,
|
|
selected_company_id,
|
|
world_issue_37_present,
|
|
world_finance_neighborhood_present,
|
|
region_record_body_present,
|
|
placed_structure_record_body_present,
|
|
infrastructure_asset_side_buffer_present,
|
|
peer_site_selector_candidate_owner_strip,
|
|
peer_site_selector_candidate_persisted_tag_hex,
|
|
peer_site_selector_candidate_selector_lane,
|
|
peer_site_selector_candidate_secondary_payload_lane,
|
|
peer_site_selector_candidate_post_secondary_byte_status,
|
|
peer_site_selector_candidate_class_identity_status,
|
|
peer_site_selector_candidate_helper_linkage,
|
|
peer_site_selector_candidate_saved_payload_summaries,
|
|
peer_site_selector_candidate_saved_payload_delta_summaries,
|
|
peer_site_selector_candidate_saved_footer_padding_summaries,
|
|
peer_site_selector_candidate_saved_companion_byte_summaries,
|
|
peer_site_selector_candidate_saved_policy_trailing_word_summaries,
|
|
peer_site_selector_candidate_saved_nonzero_companion_name_pair_summaries,
|
|
peer_site_persisted_selector_bundle_fields,
|
|
peer_site_rebuilt_transient_followon_fields,
|
|
peer_site_shellless_minimum_persisted_identity_status,
|
|
peer_site_shellless_minimum_persisted_identity_inputs,
|
|
peer_site_restore_input_fields,
|
|
peer_site_runtime_input_fields,
|
|
peer_site_runtime_reconstruction_status,
|
|
peer_site_runtime_reconstruction_steps,
|
|
near_city_acquisition_region_input_fields,
|
|
near_city_acquisition_peer_input_fields,
|
|
near_city_acquisition_company_input_fields,
|
|
near_city_acquisition_shellless_readiness_status,
|
|
near_city_acquisition_runtime_backed_input_families,
|
|
near_city_acquisition_site_owner_company_projection_status,
|
|
near_city_acquisition_site_self_id_projection_status,
|
|
near_city_acquisition_site_cached_tri_lane_projection_status,
|
|
near_city_acquisition_tri_lane_live_service_status,
|
|
near_city_acquisition_candidate_subtype_projection_status,
|
|
near_city_acquisition_backing_record_projection_status,
|
|
near_city_acquisition_nontransport_persisted_source_status,
|
|
near_city_acquisition_nontransport_persisted_source_candidates,
|
|
near_city_acquisition_tri_lane_save_shape_family_status,
|
|
near_city_acquisition_tri_lane_save_shape_family_candidates,
|
|
near_city_acquisition_tri_lane_live_owner_families,
|
|
near_city_acquisition_tri_lane_candidate_gate_fields,
|
|
near_city_acquisition_tri_lane_runtime_writer_roles,
|
|
near_city_acquisition_tri_lane_direct_caller_families,
|
|
near_city_acquisition_tri_lane_formula_input_lanes,
|
|
near_city_acquisition_projection_hypotheses,
|
|
near_city_acquisition_remaining_owner_gaps,
|
|
near_city_acquisition_region_lane_statuses,
|
|
atlas_candidate_consumers,
|
|
known_bridge_helpers,
|
|
next_owner_questions,
|
|
linked_transit_shellless_readiness_status,
|
|
linked_transit_minimum_persisted_identity_inputs,
|
|
linked_transit_live_rebuilt_cache_lanes,
|
|
linked_transit_runtime_backed_input_families,
|
|
linked_transit_remaining_owner_gaps,
|
|
companies,
|
|
notes,
|
|
}
|
|
}
|
|
|
|
pub fn inspect_save_region_service_trace_file(
|
|
path: &Path,
|
|
) -> Result<SmpRegionServiceTraceReport, Box<dyn std::error::Error>> {
|
|
let analysis = inspect_save_company_and_chairman_analysis_file(path)?;
|
|
Ok(build_region_service_trace_report(&analysis))
|
|
}
|
|
|
|
pub fn inspect_save_infrastructure_asset_trace_file(
|
|
path: &Path,
|
|
) -> Result<SmpInfrastructureAssetTraceReport, Box<dyn std::error::Error>> {
|
|
let analysis = inspect_save_company_and_chairman_analysis_file(path)?;
|
|
Ok(build_infrastructure_asset_trace_report(&analysis))
|
|
}
|
|
|
|
fn build_region_service_trace_report(
|
|
analysis: &SmpSaveCompanyChairmanAnalysisReport,
|
|
) -> SmpRegionServiceTraceReport {
|
|
let atlas_candidate_consumers = vec![
|
|
"0x00422100 periodic class-0 region picker and queue seed owner".to_string(),
|
|
"0x004337c0 queued 0x20-byte notice-node append helper".to_string(),
|
|
"0x00437c00 queued-kind dispatch owner".to_string(),
|
|
"0x004c7520 kind-7 region-focused custom-modal owner".to_string(),
|
|
"0x004358d0 pending region bonus service owner".to_string(),
|
|
"0x00438710 recurring queued-notice service owner".to_string(),
|
|
"0x00420030 / 0x00420280 city-connection peer probes".to_string(),
|
|
"0x0047efe0 placed-structure linked-company resolver".to_string(),
|
|
];
|
|
let known_owner_bridge_fields = vec![
|
|
"[region+0x25e] pending-bonus severity/source lane".to_string(),
|
|
"[region+0x276] pending bonus amount".to_string(),
|
|
"[region+0x302] completion latch".to_string(),
|
|
"[region+0x316] one-shot fallback notice latch".to_string(),
|
|
"[region+0x356] localized region name".to_string(),
|
|
"[region+0x23a] world-scalar-backed region lane used in notices".to_string(),
|
|
];
|
|
let known_bridge_helpers = vec![
|
|
"0x004207d0 city_site_format_connection_bonus_status_label".to_string(),
|
|
"0x00420030 city_connection_bonus_exists_matching_peer_site".to_string(),
|
|
"0x00420280 city_connection_bonus_select_first_matching_peer_site".to_string(),
|
|
"0x0047efe0 placed_structure_query_linked_company_id".to_string(),
|
|
"0x00480bb0 placed_structure_refresh_linked_site_display_name_and_route_anchor".to_string(),
|
|
"0x00420650 city-site local scalar refresh release-side companion".to_string(),
|
|
"0x00421510 world_region_collection_refresh_records_from_tagged_bundle".to_string(),
|
|
"0x0041f5c0 world_region_load_tagged_payload_and_profile_collection_0x37f".to_string(),
|
|
"0x00455fc0 shared region tagged-payload reload companion".to_string(),
|
|
"0x00455870 region triplet-band tagged restore callback (world-region vtable +0x48)"
|
|
.to_string(),
|
|
"0x00455930 region triplet-band tagged serializer callback (world-region vtable +0x4c)"
|
|
.to_string(),
|
|
"0x004cc930 selected-region severity/source editor helper".to_string(),
|
|
"0x00438150 fixed-region severity/source reseed owner".to_string(),
|
|
"0x00442cc0 fixed-region severity/source clamp owner".to_string(),
|
|
];
|
|
let next_owner_questions = vec![
|
|
"Which restore seam re-seeds [region+0x25e] and clears [region+0x302/+0x316] before the grounded 0x00422100 -> 0x004358d0 producer/consumer cycle runs again?".to_string(),
|
|
"Which stable region id or class discriminator survives save/load strongly enough to drive 0x004358d0 after the class-0 raster/id rebuilds are ruled out?".to_string(),
|
|
"Which later global restore continuation after 0x00444887 rehydrates [region+0x2a4] and [region+0x310/+0x338/+0x360] once the 0x00421510 -> 0x0041f5c0 -> 0x00455fc0 path and the 0x00444b90 -> 0x00420560 per-region follow-on are both ruled down?".to_string(),
|
|
"Which post-load generation owner under 0x004384d0 actually republishes acquisition-side region lanes after world_load_saved_runtime_state_bundle returns: the 319 placed-structure replay strip, the 320 building setup strip, or the 321 economy-seeding tail?".to_string(),
|
|
"How far can the grounded 0x00420030/0x00420280 plus 0x0047efe0 connection chain be rehosted directly before the transient queued-notice family matters again?".to_string(),
|
|
];
|
|
let candidate_consumer_hypotheses = vec![
|
|
SmpServiceConsumerHypothesis {
|
|
label: "pending region bonus service path".to_string(),
|
|
status: if analysis.region_record_triplets.is_some() {
|
|
"highest_priority_static_mapping_target".to_string()
|
|
} else {
|
|
"possible_consumer_family".to_string()
|
|
},
|
|
candidate_consumers: vec![
|
|
"0x004358d0 pending region bonus service owner".to_string(),
|
|
"0x00420030 / 0x00420280 city-connection peer probes".to_string(),
|
|
"0x0047efe0 placed-structure linked-company resolver".to_string(),
|
|
],
|
|
evidence: vec![
|
|
"atlas already bounds this owner as the direct consumer of [region+0x276], [region+0x302], and [region+0x316]".to_string(),
|
|
"the new region trace already proves the record envelope and profile subcollection, so the remaining gap is the separate persisted latch seam rather than the service owner".to_string(),
|
|
"direct disassembly now shows 0x004358d0 calling 0x00420030 twice plus 0x00420280, resolving the linked company through 0x0047efe0, posting company stat slot 4, and then clearing [region+0x276] while stamping [region+0x302] or [region+0x316]".to_string(),
|
|
"that same direct disassembly now also tightens the branch meaning: the linked-company branch formats the localized region-name notice from [region+0x356], posts it through 0x004554e0 and 0x0042a080, clears [region+0x276], and stamps [region+0x302]=1, while the fallback branch only runs when [region+0x316]==0 and then flips that one-shot latch to 1 before emitting its alternate notice".to_string(),
|
|
"the checked-in constructor owner 0x00421200 now also proves these latches are initialized locally at record construction time, which narrows the remaining gap to post-construction restore or rebuild rather than basic field identity".to_string(),
|
|
],
|
|
blockers: vec![
|
|
"restore seam that re-seeds [region+0x25e] and clears [region+0x302/+0x316] between service cycles".to_string(),
|
|
"stable region id or class discriminator".to_string(),
|
|
],
|
|
},
|
|
SmpServiceConsumerHypothesis {
|
|
label: "region tagged-load restore path".to_string(),
|
|
status: if analysis.region_record_triplets.is_some() {
|
|
"parallel_static_mapping_target".to_string()
|
|
} else {
|
|
"possible_consumer_family".to_string()
|
|
},
|
|
candidate_consumers: vec![
|
|
"0x00421510 world_region_collection_refresh_records_from_tagged_bundle".to_string(),
|
|
"0x0041f5c0 world_region_load_tagged_payload_and_profile_collection_0x37f".to_string(),
|
|
"0x00455fc0 shared region tagged-payload reload companion".to_string(),
|
|
],
|
|
evidence: vec![
|
|
"the checked-in function map already grounds 0x00421510 as the tagged region-collection load owner that dispatches each live record through vtable slot +0x40".to_string(),
|
|
"the checked-in function map already grounds 0x0041f5c0 as the per-record load slot that reloads the tagged payload through 0x00455fc0 and then rebuilds profile collection [region+0x37f]".to_string(),
|
|
"constructor-side evidence now proves the latches are initialized locally, so the remaining gap can legitimately be framed as post-construction restore or rebuild".to_string(),
|
|
"direct disassembly of 0x0041f590/0x0041f5b0 now proves the world-region vtable root is 0x005c9a28, so the 0x00455fc0 dispatch at slot +0x48 lands on 0x00455870 and the serializer sibling at +0x4c lands on 0x00455930".to_string(),
|
|
"direct disassembly of 0x00455870/0x00455930 now shows that callback pair only restores and serializes two helper-local three-lane scalar bands: 0x00455870 reads six dwords through 0x531150 and forwards them to 0x530720 -> [helper+0x1e2/+0x1e6/+0x1ea] and 0x52e8b0 -> [helper+0x4b/+0x4f/+0x53], while 0x00455930 writes that same pair back through 0x531030; it still does not touch acquisition-side lanes [region+0x2a4] or [region+0x310/+0x338/+0x360]".to_string(),
|
|
"direct disassembly now tightens the rest of 0x00455fc0 too: after the +0x48 callback it only runs 0x0052ebd0 to read two one-byte generic flags through 0x531150 into base object bytes [this+0x20], [this+0x8d], [this+0x5c..+0x61], [this+0x1ee], [this+0x1fa], and [this+0x3e], then opens 0x55f3 only for span accounting before returning, so the missing region latches are not hiding in the remainder of 0x00455fc0 either".to_string(),
|
|
"direct disassembly of 0x00421510 now also shows the exact tagged loop shape: it opens 0x5209, reads 0x520a, iterates live entry ordinals through 0x518380/0x518140, seeds a stack-local world-region vtable helper through 0x0041f590/0x0041f5b0, dispatches each loaded record through slot +0x40, and only then closes 0x520b".to_string(),
|
|
"direct disassembly of 0x0041f5c0 now also shows its post-0x00455fc0 work is local to the profile collection path: it clamps [region+0x31b] back to 1.0f when needed, zeroes [region+0x317], allocates one 0x88-sized helper through 0x53b070/0x518b90, stores it at [region+0x37f], loads that helper through 0x518680, and clears [region+0x385] before returning".to_string(),
|
|
"the first caller-side checkpoint above 0x00421510 is now grounded too: 0x00444887 invokes the region collection refresh inside a broader restore sequence and then immediately advances to territory_collection_refresh_records_from_tagged_bundle 0x00487c20 and support_collection_refresh_records_from_tagged_bundle 0x0040b5d0, which makes the missing latches look like a later global rebuild seam rather than hidden work inside 0x00421510 itself".to_string(),
|
|
"that broader restore strip now also has one grounded later region-local sweep: at 0x00444b08 it re-enters the live region collection through 0x00421ce0, which walks live records via 0x518380/0x518140 and dispatches 0x0041fb00 per record".to_string(),
|
|
"the checked-in atlas already grounds 0x0041fb00 as the class-0-only default-region helper under the same family, and 0x00421730 as the later raster finalizer that repopulates [world+0x212d] from class-0 region ids".to_string(),
|
|
"direct disassembly now tightens that later sweep too: 0x0041fb00 exits immediately for nonzero [region+0x23e], while 0x00421730 clears the per-cell region word at [world+0x212d]+cell*4+1, seeds cached bounds-like fields [region+0x242/+0x246/+0x24a/+0x24e/+0x252], and only then enters the class-0 path that consumes [region+0x256] and the coordinate helpers 0x00455800/0x00455810".to_string(),
|
|
"the companion region-set root is runtime-owned now too: direct disassembly of the broader bring-up strip at 0x00448740..0x0044881f shows 0x006cfc9c being allocated through 0x53b070 and constructed through 0x00487bd0 before later rebuild passes run, so the 0x00487650/0x004881b0 companion path is operating on a runtime-owned helper collection rather than a hidden save-owned latch seam".to_string(),
|
|
"the later restore-band siblings are tighter now too: 0x00487de0 clears the prior chunked border queues through 0x00533cf0, builds a small per-region id map from [region+0x00]/[region+0x35] keyed by class [region+0x31], scans the live world raster at [world+0x2131], and appends fresh border-segment rows through 0x00536ea0 without touching [region+0x25e/+0x276/+0x302/+0x316]".to_string(),
|
|
"the neighboring world-grid reseed 0x0044c4b0 is tighter now too: it clears bit 0x10 across the live grid byte at cell offset +0xe6, then walks the live region collection at 0x0062bae0, admits only class-0 records via [region+0x23e], resolves one representative center cell through 0x00455f60, and marks that same bit back on, which again reads as raster presentation state rather than pending/completion latch restore".to_string(),
|
|
"the companion region-set rebuild above that border pass is narrower now too: 0x00487650 is only a small constructor wrapper over 0x00487540 that seeds [region+0x00] from the caller-supplied id, while 0x004881b0 rebuilds [region+0x3d] from the world raster histogram, zeroes [region+0x41], folds class-0 children back into parent [region+0x41] through [region+0x35], and then tails into the border emitter on 0x006cfc9c via 0x00487de0".to_string(),
|
|
"the later class-0 batch at 0x00438087 is narrower now too: it walks live class-0 regions through 0x0062bae0, scales the mirrored severity/source pair [region+0x25a/+0x25e] from the current value using world-side factors, clamps the result, and then hands the whole collection to 0x00421c20; it never reads or writes [region+0x276/+0x302/+0x316]".to_string(),
|
|
"the follow-on 0x00421c20 is now bounded as a parameterized region-collection helper rather than a latch owner: it loops the same collection with caller-provided scalar arguments, dispatches each record through 0x004235c0, and never touches the pending/completion/one-shot lanes directly".to_string(),
|
|
"the subsequent world follow-ons are narrower too: 0x00437b20 only stages one world-side reentry guard at [world+0x46c38], iterates the live region collection through 0x00423d30, and then tails into 0x00434d40, while 0x00437220 rebuilds broader world byte-set state around [world+0x66be/+0x69db] and other global collections. Those branches are still broader runtime follow-ons, not direct owners of [region+0x276/+0x302/+0x316]".to_string(),
|
|
],
|
|
blockers: vec![
|
|
"which later restore or rebuild owner rehydrates [region+0x276/+0x302/+0x316] after the 0x00421510 -> 0x0041f5c0 -> 0x00455fc0 path completes".to_string(),
|
|
"whether [region+0x25e] severity/source and any stable region id/class discriminator are serialized elsewhere in the tagged region body or recomputed later by another post-load owner after the 0x00421ce0 -> 0x0041fb00 -> 0x00421730 class-0 raster/id sweep, 0x004881b0 companion cell-count rebuild, 0x00487de0 border rebuild, 0x0044c4b0 center-cell reseed, the 0x00438087 mirrored severity/source batch, and the 0x00421c20 -> 0x004235c0 follow-on helper loop are all ruled out as latch owners".to_string(),
|
|
],
|
|
},
|
|
SmpServiceConsumerHypothesis {
|
|
label: "periodic producer and queued-notice path".to_string(),
|
|
status: "secondary_candidate_after_pending_service".to_string(),
|
|
candidate_consumers: vec![
|
|
"0x00422100 periodic class-0 region picker and queue seed owner".to_string(),
|
|
"0x004337c0 queued 0x20-byte notice-node append helper".to_string(),
|
|
"0x00438710 recurring queued-notice service owner".to_string(),
|
|
],
|
|
evidence: vec![
|
|
"atlas ties these owners to the transient kind-7 queue family rooted at [world+0x66a6]".to_string(),
|
|
"grounded save probes now show that the ordinary-save queue family is not obviously persisted, so this looks more like runtime rebuild state than a direct save seam".to_string(),
|
|
"direct disassembly now shows 0x00422100 itself owning the pending-amount seed: it counts eligible class-0 regions with [region+0x276]==0 and [region+0x302]==0, samples one candidate, buckets [region+0x25e] against three thresholds, writes the resulting amount to [region+0x276], and then appends the kind-7 queued notice through 0x004337c0".to_string(),
|
|
],
|
|
blockers: vec![
|
|
"transient queue is not obviously persisted in ordinary saves".to_string(),
|
|
"needs the upstream restore seam for [region+0x25e/+0x302/+0x316] rather than more queue-side probing".to_string(),
|
|
],
|
|
},
|
|
SmpServiceConsumerHypothesis {
|
|
label: "later global restore continuation".to_string(),
|
|
status: "next_global_restore_handoff_target".to_string(),
|
|
candidate_consumers: vec![
|
|
"0x00444887 broader restore continuation after region refresh".to_string(),
|
|
"0x00487c20 territory_collection_refresh_records_from_tagged_bundle".to_string(),
|
|
"0x0040b5d0 support_collection_refresh_records_from_tagged_bundle".to_string(),
|
|
"0x00444b90 later per-region restore follow-on loop".to_string(),
|
|
"0x00420560 region profile-derived scalar refresh helper".to_string(),
|
|
],
|
|
evidence: vec![
|
|
"the checked-in region trace already grounds 0x00444887 as the first caller-side checkpoint above 0x00421510: it refreshes the region collection and then immediately advances into the territory and support collection refresh owners".to_string(),
|
|
"the neighboring atlas-backed restore symmetry already rules the territory side down somewhat too: 0x00487c20 only restores the live territory collection metadata/id family and its current per-entry slots 0x00487670/0x00487680 are still no-op load/save callbacks, so the territory leg now looks less likely than support or a later region-local rebuild to hide [region+0x2a4] or [region+0x310/+0x338/+0x360]".to_string(),
|
|
"the atlas-backed support seam is broader than a direct region payload owner too: 0x0040b5d0 sits over collection 0x0062b244, whose grounded live owners maintain goose-entry counters and neighboring world support lanes [world+0x4c9a/+0x4c9e/+0x4ca6/+0x4caa] plus selected support-entry state rather than an obvious per-region acquisition latch family".to_string(),
|
|
"the same grounded evidence already narrows the later per-region follow-on too: 0x00444b90 dispatches 0x00420560 over each live region after the broader restore continuation has already advanced".to_string(),
|
|
"direct disassembly already rules that per-region follow-on down as a latch owner: 0x00420560 only zeroes and recomputes [region+0x312] from the embedded profile collection [region+0x37f]/[region+0x383], revisits the linked placed-structure chain for class-mix terms, and lazily seeds the year-driven [region+0x317/+0x31b] band through 0x00420350, not [region+0x276/+0x302/+0x316]".to_string(),
|
|
"that leaves the broader 0x00444887 continuation as the next structured restore seam above the ruled-down 0x00421510 -> 0x0041f5c0 -> 0x00455fc0 path when chasing acquisition-side lanes [region+0x2a4] and [region+0x310/+0x338/+0x360]".to_string(),
|
|
],
|
|
blockers: vec![
|
|
"the concrete later-global restore owner that rehydrates [region+0x2a4] and [region+0x310/+0x338/+0x360] is still not grounded below the 0x00444887 continuation".to_string(),
|
|
"the later region-local rebuild now looks stronger than territory refresh 0x00487c20 or the broader support seam 0x0040b5d0, but the exact restore owner below the 0x00444887 continuation is still not grounded".to_string(),
|
|
],
|
|
},
|
|
SmpServiceConsumerHypothesis {
|
|
label: "post-load generation pipeline handoff".to_string(),
|
|
status: "next_post_load_owner_family".to_string(),
|
|
candidate_consumers: vec![
|
|
"0x004384d0 world_run_post_load_generation_pipeline".to_string(),
|
|
"0x004133b0 placed_structure_collection_refresh_local_runtime_records_and_position_scalars".to_string(),
|
|
"0x00421c20 world_region_collection_run_building_population_pass".to_string(),
|
|
"0x004235c0 world_region_balance_structure_demand_and_place_candidates".to_string(),
|
|
"0x00423d30 world_region_refresh_cached_category_totals_and_weight_slots".to_string(),
|
|
"0x00437b20 simulation_run_chunked_fast_forward_burst".to_string(),
|
|
],
|
|
evidence: vec![
|
|
"the checked-in shell-load subgraph and function map now place world_load_saved_runtime_state_bundle 0x00446d40 directly ahead of world_run_post_load_generation_pipeline 0x004384d0, so the first later non-hook owner family after the ruled-down 0x00444887 continuation is the post-load generation strip rather than another tagged region payload callback".to_string(),
|
|
"0x004384d0 already exposes the stage ordering tightly enough to subdivide the next search: id 319 refreshes the route-entry collection, auxiliary route trackers, and then 0x004133b0 placed-structure local-runtime replay; id 320 runs 0x00421c20(1.0, 1) for the region-owned building setup strip; id 321 runs 0x00437b20 and then sweeps regions through 0x00423d30".to_string(),
|
|
"the 319 placed-structure replay strip is now grounded as more than generic setup glue: 0x004133b0 drains queued site ids through 0x0040e450, sweeps every live placed structure through 0x0040ee10, and then reaches the already-grounded linked-site follow-on 0x00480710, which is a stronger structural bridge into acquisition-side site or peer state than the ruled-down territory/support loaders".to_string(),
|
|
"the 320 building setup strip is narrower but still relevant: 0x00421c20 dispatches every live region into 0x004235c0, and that worker consults the region profile collection [region+0x37f], the placed-instance registry 0x0062b26c, and demand-balancing helpers like 0x00422900/0x00422be0/0x00422ee0, so current evidence keeps it in the same acquisition-adjacent owner family even though it is not yet a direct writer to [region+0x2a4] or [region+0x310/+0x338/+0x360]".to_string(),
|
|
"the 321 economy-seeding tail is also now bounded as a narrower cache refresh rather than generic noise: 0x00437b20 only stages a fast-forward guard and then sweeps 0x0062bae0 through 0x00423d30, which refreshes the cached category band [region+0x27a/+0x27e/+0x282/+0x286], so it remains a weaker but still explicit post-load owner family to rule in or out before returning to the deeper 0x00446d40 continuation".to_string(),
|
|
],
|
|
blockers: vec![
|
|
"current grounded evidence still does not show which post-load subphase actually republishes [region+0x2a4] or the cached tri-lane [region+0x310/+0x338/+0x360]".to_string(),
|
|
"0x00421c20 -> 0x004235c0 and 0x00437b20 -> 0x00423d30 are still grounded as region-side demand and cache refresh passes rather than direct restore writers, so the next static pass still needs the exact later field bridge".to_string(),
|
|
],
|
|
},
|
|
SmpServiceConsumerHypothesis {
|
|
label: "queued kind-7 modal dispatch path".to_string(),
|
|
status: "shell_adjacent_reference_only".to_string(),
|
|
candidate_consumers: vec![
|
|
"0x00437c00 queued-kind dispatch owner".to_string(),
|
|
"0x004c7520 kind-7 region-focused custom-modal owner".to_string(),
|
|
],
|
|
evidence: vec![
|
|
"atlas already bounds this family as the shell-facing modal dispatch above the queued region id".to_string(),
|
|
"it is still useful as a reference owner for field identity, but not the first shellless rehost target".to_string(),
|
|
],
|
|
blockers: vec![
|
|
"full shell/dialog ownership remains out of scope".to_string(),
|
|
],
|
|
},
|
|
];
|
|
let entries = analysis
|
|
.region_record_triplets
|
|
.as_ref()
|
|
.map(|probe| {
|
|
probe.entries
|
|
.iter()
|
|
.map(|entry| SmpRegionServiceTraceEntry {
|
|
name: entry.name.clone(),
|
|
profile_collection_count: entry
|
|
.profile_collection
|
|
.as_ref()
|
|
.map(|collection| collection.live_record_count),
|
|
policy_leading_f32_0_text: format!("{:.6}", entry.policy_leading_f32_0),
|
|
policy_leading_f32_1_text: format!("{:.6}", entry.policy_leading_f32_1),
|
|
policy_leading_f32_2_text: format!("{:.6}", entry.policy_leading_f32_2),
|
|
policy_reserved_dword_hex_words: entry
|
|
.policy_reserved_dwords
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect(),
|
|
policy_trailing_word_hex: entry.policy_trailing_word_hex.clone(),
|
|
branches: vec![
|
|
build_service_trace_branch_status(
|
|
"pending_bonus_queue_seed",
|
|
"blocked_missing_pending_bonus_owner_lane",
|
|
&[
|
|
"region triplet envelope",
|
|
"embedded profile subcollection",
|
|
"policy float lanes",
|
|
],
|
|
&[
|
|
"[region+0x276] pending amount lane",
|
|
"[region+0x25e] severity/source lane",
|
|
],
|
|
&[
|
|
"0x00422100 periodic class-0 region picker and queue seed owner",
|
|
"0x004337c0 queued 0x20-byte notice-node append helper",
|
|
],
|
|
&["The queued kind-7 notice family is not obviously persisted in ordinary saves, so the pending queue must be treated as transient until a direct owner seam is found."],
|
|
),
|
|
build_service_trace_branch_status(
|
|
"city_connection_completion",
|
|
"blocked_missing_completion_and_one_shot_latches",
|
|
&[
|
|
"region triplet envelope",
|
|
"region name stem",
|
|
],
|
|
&[
|
|
"[region+0x302] completion latch",
|
|
"[region+0x316] one-shot notice latch",
|
|
"stable region id or class discriminator",
|
|
],
|
|
&[
|
|
"0x004358d0 pending region bonus service owner",
|
|
"0x00420030 / 0x00420280 city-connection peer probes",
|
|
"0x0047efe0 placed-structure linked-company resolver",
|
|
],
|
|
&["The remaining region blocker is a separate owner seam for the latches the city-connection branch reads and writes."],
|
|
),
|
|
],
|
|
})
|
|
.collect::<Vec<_>>()
|
|
})
|
|
.unwrap_or_default();
|
|
|
|
let mut notes = Vec::new();
|
|
notes.push(
|
|
"Region service trace treats the queued kind-7 notice family as transient runtime state until a persisted owner seam is found.".to_string(),
|
|
);
|
|
notes.push(
|
|
"Direct disassembly now grounds the core producer/consumer pair itself: 0x00422100 seeds [region+0x276] from the severity/source lane [region+0x25e] and appends the kind-7 notice through 0x004337c0, while 0x004358d0 consumes that amount, runs the city-connection peer probes 0x00420030/0x00420280 plus the linked-company resolver 0x0047efe0, and then stamps [region+0x302] or [region+0x316].".to_string(),
|
|
);
|
|
notes.push(
|
|
"Direct disassembly now also tightens the severity/source side itself: 0x004cc930 is a selected-region editor helper that writes [region+0x25a] and [region+0x25e] together from one integer input, while 0x00438150 and 0x00442cc0 are fixed-region global reseed/clamp owners over collection 0x0062bae0 that adjust the same mirrored pair for hardcoded region ids.".to_string(),
|
|
);
|
|
notes.push(
|
|
"Two more apparent offset hits are now ruled out as region false leads: 0x0043a5a0 is a separate constructor under vtable root 0x005ca078 that zeroes its own [this+0x302/+0x316] fields during local object setup, and 0x0045c460/0x0045c8xx is a separate vtable-0x005cb5e8 helper family whose [this+0x316] is a child-array pointer serialized through 0x61a9/0x61aa/0x61ab rather than a region latch.".to_string(),
|
|
);
|
|
notes.push(
|
|
"A direct-writer census now narrows the remaining literal offset path further: the other `0x302/0x316` writer bands at 0x0043dd45/0x0043de19/0x0043e0a7/0x0043f5bc all hang off the same non-region 0x005ca078 object family as 0x0043a5a0 through helpers 0x0043af60/0x0043b030, so the only grounded region-owned literal writes left are the constructor 0x00421200 plus the producer/consumer pair 0x00422100 and 0x004358d0.".to_string(),
|
|
);
|
|
notes.push(
|
|
"The later post-load per-region sweep is ruled down further now too: in the 0x00444887 restore strip, the follow-on loop at 0x00444b90 dispatches 0x00420560 over each live region, but that helper only zeroes and recomputes [region+0x312] from the embedded profile collection [region+0x37f]/[region+0x383] and lazily seeds the year-driven [region+0x317/+0x31b] band through 0x00420350, not [region+0x276/+0x302/+0x316].".to_string(),
|
|
);
|
|
notes.push(
|
|
"The current region seam is strong enough to prove record-envelope ownership, profile subcollection ownership, and the absence of hidden 0x55f3 tail padding on grounded saves.".to_string(),
|
|
);
|
|
if let Some(probe) = analysis.region_record_triplets.as_ref() {
|
|
let mut trailing_words = probe
|
|
.entries
|
|
.iter()
|
|
.map(|entry| entry.policy_trailing_word_hex.clone())
|
|
.collect::<Vec<_>>();
|
|
trailing_words.sort();
|
|
trailing_words.dedup();
|
|
let preview = trailing_words
|
|
.iter()
|
|
.take(4)
|
|
.cloned()
|
|
.collect::<Vec<_>>()
|
|
.join(", ");
|
|
notes.push(format!(
|
|
"Region 0x55f2 trailing-word candidates currently collapse to {} distinct value(s): {}.",
|
|
trailing_words.len(),
|
|
if preview.is_empty() {
|
|
"none".to_string()
|
|
} else {
|
|
preview
|
|
}
|
|
));
|
|
if probe.entries.iter().all(|entry| {
|
|
entry.policy_reserved_dwords.iter().all(|word| *word == 0)
|
|
&& entry.policy_trailing_word == 1
|
|
}) {
|
|
notes.push(
|
|
"Grounded region 0x55f2 fixed-policy chunks also keep all three reserved dwords at zero while the trailing word stays 0x0001, so that chunk is not currently carrying the missing latch/id discriminator."
|
|
.to_string(),
|
|
);
|
|
}
|
|
let pre_name_prefix_count = probe
|
|
.entries
|
|
.iter()
|
|
.filter(|entry| entry.pre_name_prefix_len != 0)
|
|
.count();
|
|
let unique_pre_name_prefix_lens = probe
|
|
.entries
|
|
.iter()
|
|
.map(|entry| entry.pre_name_prefix_len)
|
|
.collect::<BTreeSet<_>>()
|
|
.into_iter()
|
|
.collect::<Vec<_>>();
|
|
notes.push(format!(
|
|
"Grounded live-entry payload starts now also show {} of {} region records with nonzero bytes before the first 0x55f1 tag; unique pre-name prefix lengths are {:?}.",
|
|
pre_name_prefix_count,
|
|
probe.entries.len(),
|
|
unique_pre_name_prefix_lens
|
|
));
|
|
}
|
|
SmpRegionServiceTraceReport {
|
|
profile_family: analysis.profile_family.clone(),
|
|
region_collection_header_present: analysis.region_collection_header.is_some(),
|
|
region_record_triplet_count: analysis
|
|
.region_record_triplets
|
|
.as_ref()
|
|
.map(|probe| probe.record_count)
|
|
.unwrap_or_default(),
|
|
queued_notice_record_count: analysis
|
|
.region_queued_notice_records
|
|
.as_ref()
|
|
.map(|probe| probe.entries.len())
|
|
.unwrap_or_default(),
|
|
atlas_candidate_consumers,
|
|
known_owner_bridge_fields,
|
|
known_bridge_helpers,
|
|
next_owner_questions,
|
|
candidate_consumer_hypotheses,
|
|
entries,
|
|
notes,
|
|
}
|
|
}
|
|
|
|
fn build_infrastructure_asset_trace_report(
|
|
analysis: &SmpSaveCompanyChairmanAnalysisReport,
|
|
) -> SmpInfrastructureAssetTraceReport {
|
|
let side_buffer = analysis.placed_structure_dynamic_side_buffer.as_ref();
|
|
let alignment = analysis
|
|
.placed_structure_dynamic_side_buffer_alignment
|
|
.as_ref();
|
|
let name_pair_summaries = side_buffer
|
|
.map(|probe| probe.name_pair_summaries.as_slice())
|
|
.unwrap_or(&[]);
|
|
let bridge_like_name_pair_count = name_pair_summaries
|
|
.iter()
|
|
.filter(|summary| summary.primary_name.contains("Bridge"))
|
|
.count();
|
|
let tunnel_like_name_pair_count = name_pair_summaries
|
|
.iter()
|
|
.filter(|summary| summary.primary_name.contains("Tunnel"))
|
|
.count();
|
|
let track_cap_like_name_pair_count = name_pair_summaries
|
|
.iter()
|
|
.filter(|summary| summary.primary_name.contains("TrackCap"))
|
|
.count();
|
|
let st_only_name_pair_corpus = !name_pair_summaries.is_empty()
|
|
&& name_pair_summaries
|
|
.iter()
|
|
.all(|summary| summary.primary_name.contains("ST"))
|
|
&& !name_pair_summaries
|
|
.iter()
|
|
.any(|summary| summary.primary_name.contains("DT"));
|
|
let atlas_candidate_consumers = vec![
|
|
"0x00491c60 infrastructure tagged side-buffer serializer owner".to_string(),
|
|
"0x00493be0 infrastructure tagged side-buffer collection load owner".to_string(),
|
|
"0x004a2c80 infrastructure composition chooser owner (DT family)".to_string(),
|
|
"0x004a34e0 infrastructure composition chooser sibling (ST family)".to_string(),
|
|
"0x0048a1e0 infrastructure child attach helper".to_string(),
|
|
"0x0048dcf0 infrastructure tagged child-stream restore outer owner".to_string(),
|
|
"0x0048dd50 infrastructure child rebuild loop".to_string(),
|
|
"0x00490a3c infrastructure payload attach helper".to_string(),
|
|
"0x004559d0 infrastructure tagged string-triplet serializer".to_string(),
|
|
"0x00455870 infrastructure tagged string-triplet load companion".to_string(),
|
|
"0x00455930 infrastructure scalar-triplet serializer sibling".to_string(),
|
|
"0x00448a70 / 0x00493660 / 0x0048b660 route and world follow-on family".to_string(),
|
|
"0x004133b0 placed-structure local-runtime refresh outer owner".to_string(),
|
|
];
|
|
let known_owner_bridge_fields = vec![
|
|
"outer stream prelude [u16 child count, optional saved primary-child byte]".to_string(),
|
|
"[this+0x248] cached primary-child slot".to_string(),
|
|
"[this+0x206/+0x20a/+0x20e] route-entry resolver fields".to_string(),
|
|
"[this+0x1e2/+0x1e6/+0x1ea] published anchor triplet".to_string(),
|
|
"[this+0x4b/+0x4f/+0x53] companion local triplet lane".to_string(),
|
|
"child list [this+0x75] under the Infrastructure owner".to_string(),
|
|
"non-direct live-entry directory [collection+0x3c] with 12-byte rows (payload pointer, previous live id, next live id)".to_string(),
|
|
];
|
|
let known_bridge_helpers = vec![
|
|
"0x00493be0 tagged 0x38a5/0x38a6/0x38a7 collection load owner".to_string(),
|
|
"0x00491c60 tagged 0x38a5/0x38a6/0x38a7 collection serializer owner".to_string(),
|
|
"0x004a2c80/0x004a34e0 paired upstream infrastructure composition choosers with decoded DT/ST table families plus BallastCap/Overpass literals".to_string(),
|
|
"0x0048a340 infrastructure chooser selector setter for [0x226]/[0x219]/[0x251]/bit 0x20".to_string(),
|
|
"0x0048a6c0 per-entry serializer for outer child-count / primary-child prelude and child payload callbacks".to_string(),
|
|
"0x00490960 infrastructure child constructor / selector propagator".to_string(),
|
|
"0x00455a40 raw vtable slot +0x44 dispatch wrapper".to_string(),
|
|
"0x00455a50 raw vtable slot +0x40 dispatch wrapper with global bridge reset".to_string(),
|
|
"0x004559d0 tagged child payload serializer for 0x55f1/0x55f2/0x55f3".to_string(),
|
|
"0x00490200 infrastructure seeded-lane route/link comparator".to_string(),
|
|
"0x00518140 indexed_collection_resolve_live_entry_payload_pointer_by_live_id".to_string(),
|
|
"0x005181f0 indexed_collection_unlink_non_direct_live_entry".to_string(),
|
|
"0x00518260 indexed_collection_link_non_direct_live_entry".to_string(),
|
|
"0x00518380 indexed_collection_find_nth_live_entry_id".to_string(),
|
|
"0x00518680 indexed_collection_load_header_bitset_and_non_direct_tables".to_string(),
|
|
"0x005395d0 shared child-attach list owner".to_string(),
|
|
"0x00539530 shared position-lane seed helper".to_string(),
|
|
"0x0053a5b0 shared third position-lane seed helper".to_string(),
|
|
"0x0052e8b0 runtime_object_publish_companion_triplet_lane_4b_4f_53".to_string(),
|
|
"0x00530720 runtime_object_publish_anchor_triplet_and_optionally_rebind_world_cell_handle"
|
|
.to_string(),
|
|
"0x0048e140 / 0x0048e160 / 0x0048e180 route-entry resolver helpers".to_string(),
|
|
];
|
|
let next_owner_questions = vec![
|
|
"With 0x00491c60, 0x0048a6c0, 0x00490960, 0x00455a40, and 0x004559d0 now grounded as the full child-construction and write-side dispatch chain for the `0x38a5/0x38a6/0x38a7` family, how do the remaining compact-prefix regimes subdivide the already-mapped save-side mode families (0x0a BallastCap, 0x0b TrackCap, 0x02 Tunnel, 0x01 Bridge, with 0x03 Overpass only grounded statically) before they surface in the seeded lanes [this+0x206/+0x20a/+0x20e], the slot +0x4c serializer, and the trailing 0x52ec50 footer path?".to_string(),
|
|
"Inside the grounded overpass/ballast branch ([this+0x226]==3) of the paired chooser siblings, when do the fixed BallastCap and Overpass literals (0x5cb138/0x5cb150 and 0x5cb168/0x5cb180) fire, and does the pure BallastCap class 0x0055/0x00 stay a boundary artifact or become a real outer prelude consumed by 0x0048dcf0?".to_string(),
|
|
"Which 0x38a5 embedded name-pair groups survive into the per-child vtable +0x40 payload callbacks dispatched through 0x00455a50?".to_string(),
|
|
"After the direct route-entry bridge helpers over [this+0x206/+0x20a/+0x20e] are grounded, which later route/local-runtime owner above 0x00448a70/0x00493660/0x0048b660 still depends on the remaining mixed exact classes once [this+0x248] is demoted to child-list cache/cleanup state?".to_string(),
|
|
];
|
|
let candidate_consumer_hypotheses = vec![
|
|
SmpServiceConsumerHypothesis {
|
|
label: "infrastructure child attach/rebuild path".to_string(),
|
|
status: if side_buffer.is_some() && (bridge_like_name_pair_count > 0
|
|
|| tunnel_like_name_pair_count > 0
|
|
|| track_cap_like_name_pair_count > 0)
|
|
{
|
|
"highest_priority_static_mapping_target".to_string()
|
|
} else {
|
|
"possible_consumer_family".to_string()
|
|
},
|
|
candidate_consumers: vec![
|
|
"0x00493be0 infrastructure tagged side-buffer collection load owner".to_string(),
|
|
"0x0048a1e0 infrastructure child attach helper".to_string(),
|
|
"0x0048dcf0 infrastructure tagged child-stream restore outer owner".to_string(),
|
|
"0x0048dd50 infrastructure child rebuild loop".to_string(),
|
|
"0x00490a3c infrastructure payload attach helper".to_string(),
|
|
],
|
|
evidence: vec![
|
|
format!(
|
|
"real side-buffer name families currently count bridge/tunnel/track-cap pairs as {}/{}/{}",
|
|
bridge_like_name_pair_count,
|
|
tunnel_like_name_pair_count,
|
|
track_cap_like_name_pair_count
|
|
),
|
|
"atlas already bounds these helpers under the literal Infrastructure owner".to_string(),
|
|
"the side-buffer corpus is disjoint from the placed-structure triplet corpus, so a separate child/rebuild family is more plausible than a compact alias".to_string(),
|
|
"direct disassembly now shows 0x00493be0 opening tag family 0x38a5/0x38a6/0x38a7, reading one shared dword into the owner-local 0x90/0x94 lane, iterating each live collection entry, and dispatching every loaded infrastructure record through 0x0048dcf0 before the later follow-on owners run".to_string(),
|
|
"direct disassembly now also shows 0x00491c60 as the serializer sibling of 0x00493be0: it writes tags 0x38a5/0x38a6/0x38a7, serializes the shared owner-local dword from [this+0x90], iterates live entries through 0x00518380/0x00518140, and dispatches each entry to 0x0048a6c0".to_string(),
|
|
"direct disassembly now also grounds paired upstream chooser siblings: 0x004a2c80 routes the DT family and 0x004a34e0 routes the ST family, with both repeatedly calling 0x0048a1e0 and branching on child type codes at [this+0x226], selector bytes at [this+0x219]/[this+0x251]/[this+0x252], bit 0x20 in [this+0x24c], and follow-on owners 0x0048a340/0x0048f4c0/0x00490200/0x00490960".to_string(),
|
|
"direct disassembly now also shows 0x0048a6c0 serializing the outer per-record prelude directly: it writes the current child count as a u16, writes the saved primary-child ordinal byte derived from [this+0x248], and then serializes each child through 0x00455a40".to_string(),
|
|
"local .rdata at 0x005cfd00 now also proves the missing write-side slot directly: for Infrastructure children, 0x00455a40 lands on vtable slot +0x44 = 0x004559d0, alongside +0x40 = 0x00455fc0, +0x48 = 0x00455870, and +0x4c = 0x00455930".to_string(),
|
|
"direct disassembly now also shows 0x004559d0 writing 0x55f1, serializing the three string lanes [this+0x206/+0x20a/+0x20e], writing 0x55f2, dispatching slot +0x4c, re-entering 0x52ec50, and then writing the closing 0x55f3 tag".to_string(),
|
|
"direct disassembly now also shows the paired chooser siblings calling 0x00490960 directly on the child-construction side, alongside 0x0048a340, 0x0048f4c0, and 0x00490200".to_string(),
|
|
"direct disassembly now also shows 0x00490960 copying selector fields into the child object ([this+0x219], [this+0x251], bit 0x20 in [this+0x24c], and [this+0x226]), allocating a fresh 0x23a Infrastructure child, seeding it through 0x00455b70 with caller-supplied stem input plus fixed literal Infrastructure at 0x005cfd74, attaching it through 0x005395d0, seeding position lanes through 0x00539530/0x0053a5b0, and optionally caching it as primary child in [this+0x248]".to_string(),
|
|
"the currently grounded direct-constructor chooser branches are narrower now too: the repeated calls at 0x004a2eba/0x004a30f9/0x004a339c feed 0x00490960 with mode arg 0x0a and stem arg 0x005cb138 = BallastCapDT_Cap.3dp, so they bypass the selector-copy block at 0x004909e2 and go straight into fresh child allocation/seeding".to_string(),
|
|
"the wider direct-calls sweep now also grounds stable 0x00490960 mode families: mode 0x0b pairs with fixed TrackCapDT/ST_Cap literals at 0x0048ed01/0x0048ed20, mode 0x03 with OverpassST_section at 0x00495a44, mode 0x02 with the decoded TunnelST/TunnelDT tables and zero-stem fallbacks across 0x004a17eb/0x004a1995/0x004a1b44/0x004a1b7d/0x004a1b95, and mode 0x01 with the decoded BridgeDT/BridgeST tables plus bridge zero-stem fallbacks across 0x004a1dae/0x004a2043/0x004a2082/0x004a221e/0x004a22a5/0x004a23aa/0x004a23eb/0x004a2409/0x004a24f6".to_string(),
|
|
"objdump on 0x00490960 now also sharpens the source-side comparison for the remaining mixed exact-prefix classes: mode lives at [esp+0x10], stem at [esp+0x14], args 3/4 at [esp+0x18]/[esp+0x1c] feed 0x539530, arg 5 at [esp+0x20] feeds 0x53a5b0, arg 10 at [esp+0x34] gates whether the new child is cached into [this+0x248], and the selector-copy block at 0x004909e2..0x00490a32 reads bytes from [esp+0x28]/[esp+0x2c]/[esp+0x30] into [this+0x219]/[this+0x251]/bit0x20 in [this+0x24c]. The fixed TrackCap mode-0x0b branches at 0x0048ed01/0x0048ed20 push literals 0x005cb198/0x005cb1ac after the same pre-seeded 1,-1,-1,0,0 flag bundle, so they reach 0x490960 with arg7/arg8/arg9 = -1/-1/0 and bypass that selector-copy block because mode >= 4. The tunnel mode-0x02 family at 0x004a17eb/0x004a1995/0x004a1b44/0x004a1b7d plus zero-stem fallback 0x004a1b95 necessarily flows through the selector-copy block because mode < 4, and the objdump caller bundles show those branches reaching 0x490960 with arg8 fixed at 1, arg9 fixed at 0, and only arg7 varying through the branch-local register (ebx/ebp) before the table or fallback stem is pushed".to_string(),
|
|
"direct disassembly now also makes that tunnel-versus-track-cap residue more exact: 0x004a17eb/0x004a1995 drive mode-0x02 through TunnelDT/TunnelST tables 0x621a94/0x621a64 with arg7 entering as a one-bit selector (0 or 1) after the local sbb/inc pair; 0x004a1b44/0x004a1b7d repeat the same one-bit arg7 pattern through sibling tables 0x621a9c/0x621a6c; and the fallback 0x004a1b95 clears both stem and selector bundle entirely. By contrast, 0x0048ed01/0x0048ed20 reach mode-0x0b with the exact same 1,-1,-1,0,0 bundle and differ only by the pushed stem literal 0x005cb198 versus 0x005cb1ac.".to_string(),
|
|
"objdump on 0x00455b70 now also makes the shared child seed strip concrete: after zeroing the same [this+0x206/+0x20a/+0x20e] lanes, it copies stack args 1/2/3 into them through 0x51d820 whenever those args are non-null, so the 0x490960 call pattern seeds [0x206] from fixed payload literal 0x005c87a8, [0x20a] from the caller stem, and [0x20e] from fixed literal 0x005cfd74 = \"Infrastructure\" before 0x004559d0 later serializes those same three lanes".to_string(),
|
|
"objdump on 0x51d820 now also shows those seeded lanes are owned heap strings, not encoded ids: it frees any prior pointer through 0x5a1145, counts the incoming NUL-terminated ASCII bytes, allocates a fresh buffer through 0x5a125d, and copies the source string byte-for-byte into the destination slot".to_string(),
|
|
"objdump on 0x52ec50 now also makes the short footer bytes literal: it serializes one byte from bit 5 of [this+0x20] and one byte from bit 6 of [this+0x20] through 0x531030, so the residual compact-prefix ambiguity still lives in how those footer bits compose with the next-record prelude rather than in the seeded name lanes themselves".to_string(),
|
|
"direct disassembly now also grounds one concrete consumer strip below those footer bits: 0x00528d90 only admits the child when the explicit caller override is set, the surrounding global override byte [owner+0x3692] is set, or bit 0x20 in [child+0x20] is set; the sibling loop at 0x00529730 only takes the later 0x530280 follow-on when bit 0x40 in [child+0x20] is set".to_string(),
|
|
"that footer-bit consumer strip is tied to a broader higher-layer owner family now too: the same 0x005295f0..0x005297b7 loop repopulates a candidate cell set through 0x00533ba0, walks candidate child lists through 0x00556ef0/0x00556f00, and honors the same controller mode byte [owner+0x3692] that the checked-in atlas already ties to the world-window presentation dispatcher. So the remaining bit-0x20 question belongs to that nearby-presentation/controller family rather than to a free-floating serializer flag".to_string(),
|
|
"the neighboring helpers tighten that owner family further: atlas-backed 0x00533ba0 is the nearby-presentation cell-table helper under the layout/presenter strip, direct disassembly shows 0x00548da0 walking list root [layout+0x2593], and direct disassembly of 0x0054bab0 mutates layout slots [layout+0x2637/+0x263b/+0x2643]. That means the 0x005295f0..0x005297b7 footer-bit consumer is sitting in layout/presentation state, not in a simulation-owned infrastructure service".to_string(),
|
|
"objdump on 0x531030/0x5a464d/0x5a44a8 now also shows the infrastructure writer is not hiding another per-owner transform there: 0x531030 just forwards the caller-supplied pointer and byte count into the generic stream backend, and 0x5a44a8 is the shared chunked stream write path keyed by the stream handle rather than an infrastructure-specific encoder".to_string(),
|
|
"that caller-matrix split now rules out one easy explanation for the mixed save-side prefixes: the shared 0xff0000ff/0x0001/0xff class cannot come from selector-copy state alone, because its dominant TrackCap rows come from mode-0x0b callers that bypass selector-copy entirely while the tunnel residue comes from mode-0x02 callers that necessarily flow through it".to_string(),
|
|
"the current grounded q.gms side-buffer name corpus now maps directly onto those constructor families too: BridgeSTWood_Section.3dp aligns with mode 0x01 Bridge, TunnelSTBrick_Cap/Section.3dp with mode 0x02 Tunnel, BallastCapST_Cap.3dp with mode 0x0a BallastCap, and TrackCapST_Cap.3dp with mode 0x0b TrackCap; only the Overpass mode-0x03 family remains static-only in the current save corpus".to_string(),
|
|
"direct disassembly now also shows 0x00490200 reading the seeded lanes [this+0x206/+0x20a/+0x20e] back through the live route collection at 0x006cfca8, classifying peer relationships with [this+0x216/+0x218/+0x201/+0x202], and therefore acting as a route/link comparator above the same child payload fields that 0x004559d0 later serializes".to_string(),
|
|
"the direct route-entry bridge is tighter now too: 0x0048e140/0x0048e160/0x0048e180 simply resolve [this+0x206/+0x20a/+0x20e] through the live route collection at 0x006cfca8 and return the pointed route-entry or null, while 0x0048e1a0 walks the first two seeded lanes, resolves each peer route, and compares [peer+0x20e]/[peer+0x201] plus conditional [peer+0x206]/[peer+0x20a] against [this+0x202] before returning a boolean match".to_string(),
|
|
"the neighboring cached-primary-child path is narrower now too: 0x0048ed30 reads [this+0x248], walks child list [this+0x08] through 0x556ef0/0x556fa0, clears [this+0x248] when it matches the current child, destroys the child through 0x455d20/0x455650/0x53b080, and tears the list down through 0x556f20/0x5570b0/0x5571d0. That makes [this+0x248] a child-list cache and cleanup lane rather than the first route-entry bridge.".to_string(),
|
|
"the chooser tables now decode to concrete asset families too: 0x621a44/0x621a54 feed BridgeST caps/sections, 0x621a64 feeds TunnelST cap/section variants, 0x621a74/0x621a84 feed BridgeDT caps/sections, and 0x621a94 feeds TunnelDT variants; fixed literals 0x5cb138/0x5cb150 are BallastCapDT/ST and 0x5cb168/0x5cb180 are OverpassDT/ST".to_string(),
|
|
"the top-level chooser branches are grounded now too: [this+0x226]==1 routes bridge families, [this+0x226]==2 routes tunnel families, [this+0x226]==3 routes overpass/ballast families, and bit 0x20 in [this+0x24c] selects the cap-oriented side over the section-oriented side inside those DT/ST siblings".to_string(),
|
|
"direct disassembly now also shows 0x0048a340 as the exact chooser-state setter: its dword argument writes [this+0x226], its next two byte arguments write [this+0x219] and [this+0x251], and its final byte argument toggles bit 0x20 in [this+0x24c]".to_string(),
|
|
"the material selectors are grounded now too: in the bridge branch, [this+0x219] indexes Steel/Stone/Wood tables directly while value 2 takes the special suspension-cap path through [this+0x252]; in the tunnel branch, [this+0x251] selects Brick versus Concrete while the cap/section split comes from bit 0x20 choosing the base versus +0x8 table entry".to_string(),
|
|
"the [this+0x252] selector is partially grounded now too: when [this+0x219]==2, the chooser jump tables dispatch fixed BridgeDT/BridgeST suspension-cap literals for R10, L10, 12, 14, 16, and 18 variants instead of using the general Bridge* table families".to_string(),
|
|
side_buffer
|
|
.and_then(|probe| probe.first_record_child_count_after_owner_shared)
|
|
.map(|child_count| {
|
|
format!(
|
|
"grounded q.gms bytes now also show the first 0x38a6 record starting immediately after that shared dword with child_count={}, saved_primary_child_byte={}, and first 0x55f1 at offset +0x{:x}",
|
|
child_count,
|
|
side_buffer
|
|
.and_then(|probe| {
|
|
probe.first_record_saved_primary_child_byte_after_owner_shared_hex
|
|
.as_deref()
|
|
})
|
|
.unwrap_or("0x00"),
|
|
side_buffer
|
|
.and_then(|probe| {
|
|
probe.first_record_first_name_tag_relative_offset_after_owner_shared
|
|
})
|
|
.unwrap_or_default()
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no grounded first-record prelude summary was available after the shared 0x38a6 owner dword".to_string()
|
|
}),
|
|
"direct disassembly now shows 0x00518140 resolving a non-direct live entry through the tombstone bitset and then returning the first dword of a 12-byte row from [collection+0x3c] for the 0x38a5 path".to_string(),
|
|
"direct disassembly now also shows 0x005181f0/0x00518260 treating the same 12-byte rows as a live-entry directory: dword +0 is the payload pointer, dword +4 is previous live id, and dword +8 is next live id, with collection head/tail caches alongside them".to_string(),
|
|
"direct disassembly now also shows 0x00493be0 iterating live-entry ordinals through 0x00518380(ordinal, 0), converting each ordinal to a live id, then resolving that live id through 0x00518140 before handing the resulting payload pointer to 0x0048dcf0".to_string(),
|
|
"direct disassembly now shows 0x00518680 loading the non-direct collection header, tombstone bitset, and live-id-bound-scaled 12-byte tables for the non-direct path before 0x00493be0 starts iterating".to_string(),
|
|
"direct disassembly now also shows the shared child payload callback 0x00455fc0 opening 0x55f1, parsing three len-prefixed strings through 0x531380, opening 0x55f2, seeding the child through 0x455b70, dispatching slot +0x48, and then opening 0x55f3".to_string(),
|
|
"direct disassembly now also shows 0x00455b70 storing those three payload strings into [this+0x206/+0x20a/+0x20e], defaulting the second lane through a fixed literal when absent and defaulting the third lane back to the first string when absent".to_string(),
|
|
format!(
|
|
"current save-side probe reports {} embedded 0x55f1 rows with a third decoded string",
|
|
side_buffer
|
|
.map(|probe| probe.decoded_embedded_name_row_with_tertiary_name_count)
|
|
.unwrap_or_default()
|
|
),
|
|
format!(
|
|
"current save-side probe also reports {} complete 0x55f1/0x55f2/0x55f3 envelopes, dominant 0x55f2 chunk len 0x{:x}, and dominant 0x55f3 span 0x{:x}",
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.map(|summary| summary.row_count_with_complete_0x55f1_0x55f2_0x55f3_envelope)
|
|
.unwrap_or_default(),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.dominant_policy_chunk_len)
|
|
.unwrap_or_default(),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.dominant_profile_chunk_len)
|
|
.unwrap_or_default()
|
|
),
|
|
"direct disassembly now also shows the post-+0x48 helper pair 0x52ebd0/0x52ec50 loading and serializing two single-byte lanes around the trailing 0x55f3 tag while folding them into bits 0x20 and 0x40 of [this+0x20]".to_string(),
|
|
format!(
|
|
"current save-side probe reports {} rows with the short 0x06-byte trailing span; dominant short flag pair is {}/{} x{}",
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.short_profile_flag_pair_summary.as_ref())
|
|
.map(|summary| summary.row_count_with_0x06_profile_span)
|
|
.unwrap_or_default(),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.short_profile_flag_pair_summary.as_ref())
|
|
.and_then(|summary| summary.dominant_flag_pair.as_ref())
|
|
.map(|pair| pair.first_flag_byte_hex.as_str())
|
|
.unwrap_or("0x00"),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.short_profile_flag_pair_summary.as_ref())
|
|
.and_then(|summary| summary.dominant_flag_pair.as_ref())
|
|
.map(|pair| pair.second_flag_byte_hex.as_str())
|
|
.unwrap_or("0x00"),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.short_profile_flag_pair_summary.as_ref())
|
|
.and_then(|summary| summary.dominant_flag_pair.as_ref())
|
|
.map(|pair| pair.count)
|
|
.unwrap_or_default()
|
|
),
|
|
"direct disassembly now also shows 0x455870 consuming six 4-byte lanes from the fixed 0x55f2 chunk and forwarding them into 0x530720 then 0x52e8b0, while 0x455930 serializes the same six dword lanes back through 0x531030".to_string(),
|
|
format!(
|
|
"current save-side probe reports {} fixed 0x1a policy rows; dominant trailing word is {} x{}",
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.fixed_policy_summary.as_ref())
|
|
.map(|summary| summary.row_count_with_0x1a_policy_chunk)
|
|
.unwrap_or_default(),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.fixed_policy_summary.as_ref())
|
|
.and_then(|summary| summary.dominant_trailing_word_hex.as_deref())
|
|
.unwrap_or("0x0000"),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.fixed_policy_summary.as_ref())
|
|
.map(|summary| summary.dominant_trailing_word_count)
|
|
.unwrap_or_default()
|
|
),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.fixed_policy_summary.as_ref())
|
|
.and_then(|summary| {
|
|
summary.compact_prefix_correlations.iter().find(|entry| {
|
|
entry.prefix_leading_dword == 0xff00_00ff
|
|
&& entry.prefix_trailing_word == 0x0001
|
|
&& entry.prefix_separator_byte == 0xff
|
|
})
|
|
})
|
|
.map(|correlation| {
|
|
format!(
|
|
"the fixed 0x55f2 lane for exact 0xff0000ff/0x0001/0xff is now explicit too: unique policy tuples={}, dominant mode={:?} x{}, dominant tuple=({:?} | {:?} | {:?}) x{}, and sample rows={:?}",
|
|
correlation.unique_policy_tuple_count,
|
|
correlation.dominant_mode_family,
|
|
correlation.dominant_mode_family_count,
|
|
correlation.dominant_first_triplet_dwords_hex,
|
|
correlation.dominant_second_triplet_dwords_hex,
|
|
correlation.dominant_trailing_word_hex,
|
|
correlation.dominant_policy_tuple_count,
|
|
correlation
|
|
.sample_rows
|
|
.iter()
|
|
.map(|sample| format!(
|
|
"{}:{:?}/{:?}",
|
|
sample.name_tag_relative_offset,
|
|
sample.primary_name,
|
|
sample.secondary_name
|
|
))
|
|
.collect::<Vec<_>>()
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no fixed-policy compact-prefix correlation was available for 0xff0000ff/0x0001/0xff".to_string()
|
|
}),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.fixed_policy_summary.as_ref())
|
|
.and_then(|summary| {
|
|
summary.compact_prefix_correlations.iter().find(|entry| {
|
|
entry.prefix_leading_dword == 0x0000_55f3
|
|
&& entry.prefix_trailing_word == 0x0001
|
|
&& entry.prefix_separator_byte == 0xff
|
|
})
|
|
})
|
|
.map(|correlation| {
|
|
format!(
|
|
"the fixed 0x55f2 lane for exact 0x000055f3/0x0001/0xff is now explicit too: unique policy tuples={}, dominant mode={:?} x{}, dominant tuple=({:?} | {:?} | {:?}) x{}, and sample rows={:?}",
|
|
correlation.unique_policy_tuple_count,
|
|
correlation.dominant_mode_family,
|
|
correlation.dominant_mode_family_count,
|
|
correlation.dominant_first_triplet_dwords_hex,
|
|
correlation.dominant_second_triplet_dwords_hex,
|
|
correlation.dominant_trailing_word_hex,
|
|
correlation.dominant_policy_tuple_count,
|
|
correlation
|
|
.sample_rows
|
|
.iter()
|
|
.map(|sample| format!(
|
|
"{}:{:?}/{:?}",
|
|
sample.name_tag_relative_offset,
|
|
sample.primary_name,
|
|
sample.secondary_name
|
|
))
|
|
.collect::<Vec<_>>()
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no fixed-policy compact-prefix correlation was available for 0x000055f3/0x0001/0xff".to_string()
|
|
}),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.dominant_profile_span_class_summary.as_ref())
|
|
.map(|summary| {
|
|
format!(
|
|
"the dominant 0x{:x}-byte post-profile class is now narrowed too: dominant name pair is {:?}/{:?} x{}, dominant compact prefix is {}/{}/{} x{}, and dominant prelude candidate is {}/{} x{} across {} rows",
|
|
summary.profile_chunk_len_to_next_name_or_end,
|
|
summary.dominant_primary_name,
|
|
summary.dominant_secondary_name,
|
|
summary.dominant_name_pair_count,
|
|
summary
|
|
.dominant_prefix_leading_dword_hex
|
|
.as_deref()
|
|
.unwrap_or("0x00000000"),
|
|
summary
|
|
.dominant_prefix_trailing_word_hex
|
|
.as_deref()
|
|
.unwrap_or("0x0000"),
|
|
summary
|
|
.dominant_prefix_separator_byte_hex
|
|
.as_deref()
|
|
.unwrap_or("0x00"),
|
|
summary.dominant_prefix_count,
|
|
summary
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.child_count_candidate_hex.as_str())
|
|
.unwrap_or("0x0000"),
|
|
summary
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.saved_primary_child_byte_candidate_hex.as_str())
|
|
.unwrap_or("0x00"),
|
|
summary
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.count)
|
|
.unwrap_or_default(),
|
|
summary.row_count
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no dominant post-profile class summary was available for the embedded 0x55f3 spans".to_string()
|
|
}),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.dominant_profile_span_class_summary.as_ref())
|
|
.map(|summary| {
|
|
format!(
|
|
"the dominant post-profile outliers are now explicit too: name-pair counts={:?}, compact-prefix counts={:?}, candidate-pattern counts={:?}",
|
|
summary
|
|
.name_pair_summaries
|
|
.iter()
|
|
.map(|entry| format!(
|
|
"{:?}/{:?}:{}",
|
|
entry.primary_name, entry.secondary_name, entry.count
|
|
))
|
|
.collect::<Vec<_>>(),
|
|
summary
|
|
.compact_prefix_pattern_summaries
|
|
.iter()
|
|
.map(|entry| format!(
|
|
"{}/{}/{}:{}",
|
|
entry.prefix_leading_dword_hex,
|
|
entry.prefix_trailing_word_hex,
|
|
entry.prefix_separator_byte_hex,
|
|
entry.count
|
|
))
|
|
.collect::<Vec<_>>(),
|
|
summary
|
|
.candidate_pattern_summaries
|
|
.iter()
|
|
.map(|entry| format!(
|
|
"{}/{}:{}",
|
|
entry.child_count_candidate_hex,
|
|
entry.saved_primary_child_byte_candidate_hex,
|
|
entry.count
|
|
))
|
|
.collect::<Vec<_>>()
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no dominant post-profile outlier breakdown was available".to_string()
|
|
}),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.name_prelude_candidate_summary.as_ref())
|
|
.map(|summary| {
|
|
format!(
|
|
"candidate-pattern correlations now split the remaining prelude classes cleanly too: {:?}",
|
|
summary
|
|
.candidate_pattern_correlations
|
|
.iter()
|
|
.map(|entry| format!(
|
|
"{}/{} rows={} dominant-name={:?}/{:?} x{} dominant-prev-span={:?} x{}",
|
|
entry.child_count_candidate_hex,
|
|
entry.saved_primary_child_byte_candidate_hex,
|
|
entry.row_count,
|
|
entry.dominant_primary_name,
|
|
entry.dominant_secondary_name,
|
|
entry.dominant_name_pair_count,
|
|
entry.dominant_profile_span,
|
|
entry.dominant_profile_span_count
|
|
))
|
|
.collect::<Vec<_>>()
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no candidate-pattern correlation summary was available".to_string()
|
|
}),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.name_prelude_candidate_summary.as_ref())
|
|
.map(|summary| {
|
|
format!(
|
|
"mode-family correlations now also split the candidate patterns directly: {:?}",
|
|
summary
|
|
.candidate_pattern_correlations
|
|
.iter()
|
|
.map(|entry| format!(
|
|
"{}/{} rows={} dominant-mode={:?} x{} mode-counts={:?}",
|
|
entry.child_count_candidate_hex,
|
|
entry.saved_primary_child_byte_candidate_hex,
|
|
entry.row_count,
|
|
entry.dominant_mode_family,
|
|
entry.dominant_mode_family_count,
|
|
entry
|
|
.mode_family_counts
|
|
.iter()
|
|
.map(|mode| format!(
|
|
"{}:{}",
|
|
mode.mode_family, mode.count
|
|
))
|
|
.collect::<Vec<_>>()
|
|
))
|
|
.collect::<Vec<_>>()
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no mode-family correlation summary was available for the prelude candidates".to_string()
|
|
}),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.name_prelude_candidate_summary.as_ref())
|
|
.map(|summary| {
|
|
format!(
|
|
"profile-span mode-family correlations now also split the previous 0x55f3 spans directly: {:?}",
|
|
summary
|
|
.profile_span_correlations
|
|
.iter()
|
|
.map(|entry| format!(
|
|
"span=0x{:x} rows={} dominant-mode={:?} x{} mode-counts={:?}",
|
|
entry.previous_profile_chunk_len_to_next_name_or_end,
|
|
entry.row_count,
|
|
entry.dominant_mode_family,
|
|
entry.dominant_mode_family_count,
|
|
entry
|
|
.mode_family_counts
|
|
.iter()
|
|
.map(|mode| format!(
|
|
"{}:{}",
|
|
mode.mode_family, mode.count
|
|
))
|
|
.collect::<Vec<_>>()
|
|
))
|
|
.collect::<Vec<_>>()
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no profile-span mode-family correlation summary was available".to_string()
|
|
}),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.name_prelude_candidate_summary.as_ref())
|
|
.map(|summary| {
|
|
format!(
|
|
"exact compact-prefix correlations now split the residual prelude classes directly: {:?}",
|
|
summary
|
|
.compact_prefix_correlations
|
|
.iter()
|
|
.map(|entry| format!(
|
|
"{}/{}/{} rows={} dominant-name={:?}/{:?} x{} dominant-span={:?} x{} dominant-candidate={:?} dominant-mode={:?} x{}",
|
|
entry.prefix_leading_dword_hex,
|
|
entry.prefix_trailing_word_hex,
|
|
entry.prefix_separator_byte_hex,
|
|
entry.row_count,
|
|
entry.dominant_primary_name,
|
|
entry.dominant_secondary_name,
|
|
entry.dominant_name_pair_count,
|
|
entry.dominant_profile_span,
|
|
entry.dominant_profile_span_count,
|
|
entry
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| format!(
|
|
"{}/{}:{}",
|
|
pattern.child_count_candidate_hex,
|
|
pattern.saved_primary_child_byte_candidate_hex,
|
|
pattern.count
|
|
)),
|
|
entry.dominant_mode_family,
|
|
entry.dominant_mode_family_count
|
|
))
|
|
.collect::<Vec<_>>()
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no compact-prefix correlation summary was available for the prelude candidates".to_string()
|
|
}),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.name_prelude_candidate_summary.as_ref())
|
|
.and_then(|summary| {
|
|
summary.candidate_pattern_correlations.iter().find(|entry| {
|
|
entry.child_count_candidate == 2
|
|
&& entry.saved_primary_child_byte_candidate == 0xff
|
|
&& entry.dominant_primary_name.as_deref()
|
|
== Some("BridgeSTWood_Section.3dp")
|
|
&& entry.dominant_secondary_name.as_deref()
|
|
== Some("Infrastructure")
|
|
})
|
|
})
|
|
.map(|correlation| {
|
|
format!(
|
|
"the bridge-only two-child class is now grounded save-side too: candidate pattern {}/{} spans {} rows, stays pure {:?}/{:?}, and the dominant prior profile span is {:?} x{}",
|
|
correlation.child_count_candidate_hex,
|
|
correlation.saved_primary_child_byte_candidate_hex,
|
|
correlation.row_count,
|
|
correlation.dominant_primary_name,
|
|
correlation.dominant_secondary_name,
|
|
correlation.dominant_profile_span,
|
|
correlation.dominant_profile_span_count
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no grounded pure bridge-only two-child candidate class was available in the prelude correlations".to_string()
|
|
}),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.name_prelude_candidate_summary.as_ref())
|
|
.and_then(|summary| {
|
|
summary
|
|
.profile_span_correlations
|
|
.iter()
|
|
.find(|row| row.previous_profile_chunk_len_to_next_name_or_end == 3)
|
|
})
|
|
.map(|correlation| {
|
|
format!(
|
|
"current save-side probe now also shows the short 0x03-byte post-profile gaps collapsing cleanly to the next-record prelude: dominant candidate pattern is {}/{} x{} across {} rows, mode counts={:?}, prefix counts={:?}",
|
|
correlation
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.child_count_candidate_hex.as_str())
|
|
.unwrap_or("0x0000"),
|
|
correlation
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.saved_primary_child_byte_candidate_hex.as_str())
|
|
.unwrap_or("0x00"),
|
|
correlation
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.count)
|
|
.unwrap_or_default(),
|
|
correlation.row_count,
|
|
correlation
|
|
.mode_family_counts
|
|
.iter()
|
|
.map(|mode| format!("{}:{}", mode.mode_family, mode.count))
|
|
.collect::<Vec<_>>(),
|
|
correlation
|
|
.compact_prefix_pattern_summaries
|
|
.iter()
|
|
.map(|prefix| format!(
|
|
"{}/{}/{}:{}",
|
|
prefix.prefix_leading_dword_hex,
|
|
prefix.prefix_trailing_word_hex,
|
|
prefix.prefix_separator_byte_hex,
|
|
prefix.count
|
|
))
|
|
.collect::<Vec<_>>()
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no grounded 0x03-byte post-profile span correlation was available for the prelude candidates".to_string()
|
|
}),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.name_prelude_candidate_summary.as_ref())
|
|
.and_then(|summary| {
|
|
summary.compact_prefix_correlations.iter().find(|entry| {
|
|
entry.prefix_leading_dword == 0xff00_00ff
|
|
&& entry.prefix_trailing_word == 0x0001
|
|
&& entry.prefix_separator_byte == 0xff
|
|
})
|
|
})
|
|
.map(|correlation| {
|
|
format!(
|
|
"the exact 0xff0000ff/0x0001/0xff compact-prefix class is now explicit: dominant name={:?}/{:?} x{}, dominant span={:?} x{}, dominant prelude={}/{} x{}, mode counts={:?}, name-pair counts={:?}, span counts={:?}, previous short-flag pairs={:?} across {} rows, and sample rows={:?}",
|
|
correlation.dominant_primary_name,
|
|
correlation.dominant_secondary_name,
|
|
correlation.dominant_name_pair_count,
|
|
correlation.dominant_profile_span,
|
|
correlation.dominant_profile_span_count,
|
|
correlation
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.child_count_candidate_hex.as_str())
|
|
.unwrap_or("0x0000"),
|
|
correlation
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.saved_primary_child_byte_candidate_hex.as_str())
|
|
.unwrap_or("0x00"),
|
|
correlation
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.count)
|
|
.unwrap_or_default(),
|
|
correlation
|
|
.mode_family_counts
|
|
.iter()
|
|
.map(|mode| format!("{}:{}", mode.mode_family, mode.count))
|
|
.collect::<Vec<_>>(),
|
|
correlation
|
|
.name_pair_summaries
|
|
.iter()
|
|
.map(|entry| format!(
|
|
"{:?}/{:?}:{}",
|
|
entry.primary_name, entry.secondary_name, entry.count
|
|
))
|
|
.collect::<Vec<_>>(),
|
|
correlation
|
|
.profile_span_counts
|
|
.iter()
|
|
.map(|entry| format!(
|
|
"0x{:x}:{}",
|
|
entry.previous_profile_chunk_len_to_next_name_or_end,
|
|
entry.count
|
|
))
|
|
.collect::<Vec<_>>(),
|
|
correlation
|
|
.previous_short_profile_flag_pair_counts
|
|
.iter()
|
|
.map(|entry| format!(
|
|
"{}/{}:{}",
|
|
entry.first_flag_byte_hex,
|
|
entry.second_flag_byte_hex,
|
|
entry.count
|
|
))
|
|
.collect::<Vec<_>>(),
|
|
correlation.rows_with_previous_short_profile_flag_pair,
|
|
correlation
|
|
.sample_rows
|
|
.iter()
|
|
.map(|sample| format!(
|
|
"{}:{:?}/{:?}",
|
|
sample.name_tag_relative_offset,
|
|
sample.primary_name,
|
|
sample.secondary_name
|
|
))
|
|
.collect::<Vec<_>>()
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no grounded 0xff0000ff/0x0001/0xff compact-prefix correlation was available".to_string()
|
|
}),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.name_prelude_candidate_summary.as_ref())
|
|
.and_then(|summary| {
|
|
summary.compact_prefix_correlations.iter().find(|entry| {
|
|
entry.prefix_leading_dword == 0x0000_55f3
|
|
&& entry.prefix_trailing_word == 0x0001
|
|
&& entry.prefix_separator_byte == 0xff
|
|
})
|
|
})
|
|
.map(|correlation| {
|
|
format!(
|
|
"the exact 0x000055f3/0x0001/0xff compact-prefix class is now explicit too: dominant name={:?}/{:?} x{}, dominant span={:?} x{}, dominant prelude={}/{} x{}, mode counts={:?}, name-pair counts={:?}, previous short-flag pairs={:?} across {} rows, and sample rows={:?}",
|
|
correlation.dominant_primary_name,
|
|
correlation.dominant_secondary_name,
|
|
correlation.dominant_name_pair_count,
|
|
correlation.dominant_profile_span,
|
|
correlation.dominant_profile_span_count,
|
|
correlation
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.child_count_candidate_hex.as_str())
|
|
.unwrap_or("0x0000"),
|
|
correlation
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.saved_primary_child_byte_candidate_hex.as_str())
|
|
.unwrap_or("0x00"),
|
|
correlation
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.count)
|
|
.unwrap_or_default(),
|
|
correlation
|
|
.mode_family_counts
|
|
.iter()
|
|
.map(|mode| format!("{}:{}", mode.mode_family, mode.count))
|
|
.collect::<Vec<_>>(),
|
|
correlation
|
|
.name_pair_summaries
|
|
.iter()
|
|
.map(|entry| format!(
|
|
"{:?}/{:?}:{}",
|
|
entry.primary_name, entry.secondary_name, entry.count
|
|
))
|
|
.collect::<Vec<_>>(),
|
|
correlation
|
|
.previous_short_profile_flag_pair_counts
|
|
.iter()
|
|
.map(|entry| format!(
|
|
"{}/{}:{}",
|
|
entry.first_flag_byte_hex,
|
|
entry.second_flag_byte_hex,
|
|
entry.count
|
|
))
|
|
.collect::<Vec<_>>(),
|
|
correlation.rows_with_previous_short_profile_flag_pair,
|
|
correlation
|
|
.sample_rows
|
|
.iter()
|
|
.map(|sample| format!(
|
|
"{}:{:?}/{:?}",
|
|
sample.name_tag_relative_offset,
|
|
sample.primary_name,
|
|
sample.secondary_name
|
|
))
|
|
.collect::<Vec<_>>()
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no grounded 0x000055f3/0x0001/0xff compact-prefix correlation was available".to_string()
|
|
}),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.name_prelude_candidate_summary.as_ref())
|
|
.and_then(|summary| {
|
|
summary.compact_prefix_correlations.iter().find(|entry| {
|
|
entry.prefix_leading_dword == 0xff00_00ff
|
|
&& entry.prefix_trailing_word == 0x0002
|
|
&& entry.prefix_separator_byte == 0xff
|
|
})
|
|
})
|
|
.map(|correlation| {
|
|
format!(
|
|
"the exact 0xff0000ff/0x0002/0xff compact-prefix class is now explicit too: dominant name={:?}/{:?} x{}, dominant span={:?} x{}, dominant prelude={}/{} x{}, mode counts={:?}, span counts={:?}, and sample rows={:?}",
|
|
correlation.dominant_primary_name,
|
|
correlation.dominant_secondary_name,
|
|
correlation.dominant_name_pair_count,
|
|
correlation.dominant_profile_span,
|
|
correlation.dominant_profile_span_count,
|
|
correlation
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.child_count_candidate_hex.as_str())
|
|
.unwrap_or("0x0000"),
|
|
correlation
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.saved_primary_child_byte_candidate_hex.as_str())
|
|
.unwrap_or("0x00"),
|
|
correlation
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.count)
|
|
.unwrap_or_default(),
|
|
correlation
|
|
.mode_family_counts
|
|
.iter()
|
|
.map(|mode| format!("{}:{}", mode.mode_family, mode.count))
|
|
.collect::<Vec<_>>(),
|
|
correlation
|
|
.profile_span_counts
|
|
.iter()
|
|
.map(|entry| format!(
|
|
"0x{:x}:{}",
|
|
entry.previous_profile_chunk_len_to_next_name_or_end,
|
|
entry.count
|
|
))
|
|
.collect::<Vec<_>>(),
|
|
correlation
|
|
.sample_rows
|
|
.iter()
|
|
.map(|sample| format!(
|
|
"{}:{:?}/{:?}",
|
|
sample.name_tag_relative_offset,
|
|
sample.primary_name,
|
|
sample.secondary_name
|
|
))
|
|
.collect::<Vec<_>>()
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no grounded 0xff0000ff/0x0002/0xff compact-prefix correlation was available".to_string()
|
|
}),
|
|
"cross-save q/p corpus checks now sharpen the mixed exact classes further without changing their identity: 0x000055f3/0x0001/0xff stays on prelude 0x0001/0xff with fixed short-flag pair 0x01/0x00 and fixed post-profile span 0x03 in both saves, while 0xff0000ff/0x0001/0xff stays on prelude 0x0001/0xff with fixed short-flag pair 0x00/0x00 and widely scattered post-profile spans in both saves".to_string(),
|
|
"that cross-save split keeps the semantic mix stable too: 0x000055f3/0x0001/0xff remains tunnel-dominant with only a small TrackCap residue, while 0xff0000ff/0x0001/0xff remains TrackCap-dominant with the same small tunnel-cap/section residue. So the next infrastructure question is no longer broad class identity; it is what the two short-flag families mean and why a minority of tunnel rows take the sparse 0xff0000ff outlier class instead of the stable span-0x03 tunnel family".to_string(),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.name_prelude_candidate_summary.as_ref())
|
|
.and_then(|summary| {
|
|
summary
|
|
.profile_span_correlations
|
|
.iter()
|
|
.find(|row| row.previous_profile_chunk_len_to_next_name_or_end == 0x27)
|
|
})
|
|
.map(|correlation| {
|
|
format!(
|
|
"the sparse 0x27 post-profile outlier is now explicit too: mode counts={:?}, prefix counts={:?}, sample rows={:?}",
|
|
correlation
|
|
.mode_family_counts
|
|
.iter()
|
|
.map(|mode| format!("{}:{}", mode.mode_family, mode.count))
|
|
.collect::<Vec<_>>(),
|
|
correlation
|
|
.compact_prefix_pattern_summaries
|
|
.iter()
|
|
.map(|prefix| format!(
|
|
"{}/{}/{}:{}",
|
|
prefix.prefix_leading_dword_hex,
|
|
prefix.prefix_trailing_word_hex,
|
|
prefix.prefix_separator_byte_hex,
|
|
prefix.count
|
|
))
|
|
.collect::<Vec<_>>(),
|
|
correlation
|
|
.sample_rows
|
|
.iter()
|
|
.map(|sample| format!(
|
|
"{}:{:?}/{:?}@{}/{}/{}",
|
|
sample.name_tag_relative_offset,
|
|
sample.primary_name,
|
|
sample.secondary_name,
|
|
sample.prefix_leading_dword_hex,
|
|
sample.prefix_trailing_word_hex,
|
|
sample.prefix_separator_byte_hex
|
|
))
|
|
.collect::<Vec<_>>()
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no grounded 0x27 post-profile outlier correlation was available for the prelude candidates".to_string()
|
|
}),
|
|
side_buffer
|
|
.and_then(|probe| probe.payload_envelope_summary.as_ref())
|
|
.and_then(|summary| summary.name_prelude_candidate_summary.as_ref())
|
|
.and_then(|summary| {
|
|
summary
|
|
.profile_span_correlations
|
|
.iter()
|
|
.find(|row| row.previous_profile_chunk_len_to_next_name_or_end == 0)
|
|
})
|
|
.map(|correlation| {
|
|
format!(
|
|
"the zero-length post-profile class is now a separate grounded outlier: dominant candidate pattern is {}/{} x{} across {} rows",
|
|
correlation
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.child_count_candidate_hex.as_str())
|
|
.unwrap_or("0x0000"),
|
|
correlation
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.saved_primary_child_byte_candidate_hex.as_str())
|
|
.unwrap_or("0x00"),
|
|
correlation
|
|
.dominant_candidate_pattern
|
|
.as_ref()
|
|
.map(|pattern| pattern.count)
|
|
.unwrap_or_default(),
|
|
correlation.row_count
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no grounded zero-length post-profile span correlation was available for the prelude candidates".to_string()
|
|
}),
|
|
"direct disassembly now also shows 0x530720 publishing the first fixed-triplet lane into [this+0x1e2/+0x1e6/+0x1ea], while 0x52e8b0 publishes the second fixed-triplet lane into [this+0x4b/+0x4f/+0x53] and sets bit 0x02".to_string(),
|
|
"direct disassembly now also shows the outer owner at 0x0048dcf0 reading one u16 child count through 0x531150 into the stream prelude, zeroing [this+0x08], and conditionally reading one saved primary-child byte before the per-child callback loop runs".to_string(),
|
|
side_buffer
|
|
.and_then(|probe| probe.live_entry_prelude_summary.as_ref())
|
|
.map(|summary| {
|
|
format!(
|
|
"the widened save-side probe now also decodes {} live-entry payload starts inside the records span; dominant child count={} x{}, dominant saved primary-child byte={} x{}, and {} payloads reach the first 0x55f1 child callback at offset +0x3",
|
|
summary.rows_with_payload_pointer_inside_records_span,
|
|
summary.dominant_child_count.unwrap_or_default(),
|
|
summary.dominant_child_count_count,
|
|
summary
|
|
.dominant_saved_primary_child_byte_hex
|
|
.as_deref()
|
|
.unwrap_or("0x00"),
|
|
summary.dominant_saved_primary_child_byte_count,
|
|
summary.rows_with_first_name_tag_at_offset_3
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no live-entry payload-start summary was available for the outer prelude".to_string()
|
|
}),
|
|
"local .rdata at 0x005cfd00 now also proves the infrastructure child table uses the shared tagged callback strip directly: slot +0x40 = 0x455fc0, slot +0x44 = 0x4559d0, slot +0x48 = 0x455870, and slot +0x4c = 0x455930".to_string(),
|
|
"direct disassembly now shows 0x0048a1e0 cloning the first child triplet bands through 0x52e880/0x52e720, destroying the prior child, seeding a new literal Infrastructure child through 0x455b70 with payload seed 0x5c87a8, attaching through 0x5395d0 or 0x53a5d0, and republishing the two bands through 0x52e8b0/0x530720".to_string(),
|
|
"direct disassembly now also shows the outer owner at 0x0048dcf0 reading a child count plus optional primary-child ordinal from the tagged stream through 0x531150, zeroing [this+0x08], dispatching each fresh child through 0x455a50 -> vtable slot +0x40, culling ordinals above 5, and restoring cached primary-child slot [this+0x248] from the saved ordinal".to_string(),
|
|
"the smaller attach primitive 0x00490a3c no longer looks like the semantic fork by itself: it just allocates one literal Infrastructure child, seeds it through 0x455b70 with caller-provided stem input, attaches it through 0x5395d0, seeds position lanes through 0x539530/0x53a5b0, and optionally caches it as primary child".to_string(),
|
|
],
|
|
blockers: vec![
|
|
"how the remaining mixed exact compact-prefix classes map back onto constructor semantics now that the whole prelude corpus is split directly: 0xff0000ff/0x0002/0xff is pure bridge, 0xff000000/{0x0001,0x0002}/0xff are pure bridge, 0xf3010100/0x0055/0x00 is pure ballast-cap, and 0x0005d368/0x0001/0xff is pure track-cap, leaving only 0xff0000ff/0x0001/0xff and 0x000055f3/0x0001/0xff as the mixed residual classes".to_string(),
|
|
"how the payload streams reached through 0x00518380 -> 0x00518140 align with the embedded 0x55f1 name-pair groups and compact-prefix regimes surfaced by the save-side probe".to_string(),
|
|
"how the observed 0x55f3-to-next-0x55f1 gaps partition between the two 0x52ebd0 flag bytes and the next-record prelude now that 0x0048a6c0 is grounded as the writer for the outer child-count / primary-child prelude".to_string(),
|
|
"which fields written through the grounded 0x00490960 -> 0x004559d0 -> slot +0x4c -> 0x52ec50 chain retain the 0x38a5 embedded name-pair semantics before route/local-runtime follow-ons take over".to_string(),
|
|
],
|
|
},
|
|
SmpServiceConsumerHypothesis {
|
|
label: "infrastructure serializer/load companion path".to_string(),
|
|
status: if side_buffer.is_some() {
|
|
"strong_static_mapping_candidate".to_string()
|
|
} else {
|
|
"possible_consumer_family".to_string()
|
|
},
|
|
candidate_consumers: vec![
|
|
"0x004559d0 infrastructure tagged string-triplet serializer".to_string(),
|
|
"0x00455870 infrastructure tagged string-triplet load companion".to_string(),
|
|
"0x00455930 infrastructure scalar-triplet serializer sibling".to_string(),
|
|
],
|
|
evidence: vec![
|
|
"atlas already bounds the serializer/load strip around the Infrastructure owner and the same 0x55f1/0x55f2/0x55f3 tag family".to_string(),
|
|
"local .rdata at 0x005cfd00 now proves the infrastructure child vtable points straight at 0x455fc0/0x4559d0/0x455870/0x455930 for the load, tagged serializer, triplet-restore, and scalar serializer slots".to_string(),
|
|
"the save-side side-buffer carries embedded dual-name rows plus compact prefixes, which is compatible with a serializer-side bridge".to_string(),
|
|
],
|
|
blockers: vec![
|
|
"which exact 0x38a5 rows belong to shared 0x55f1/0x55f2/0x55f3 child records versus outer collection metadata, now that the concrete write-side slot +0x44 serializer is grounded as 0x004559d0".to_string(),
|
|
],
|
|
},
|
|
SmpServiceConsumerHypothesis {
|
|
label: "route/local-runtime follow-on path".to_string(),
|
|
status: if side_buffer.is_some() {
|
|
"secondary_candidate_after_attach_rebuild".to_string()
|
|
} else {
|
|
"possible_consumer_family".to_string()
|
|
},
|
|
candidate_consumers: vec![
|
|
"0x00448a70 / 0x00493660 / 0x0048b660 route and world follow-on family".to_string(),
|
|
"0x004133b0 placed-structure local-runtime refresh outer owner".to_string(),
|
|
],
|
|
evidence: vec![
|
|
"atlas ties the Infrastructure rebuild loop to later route-side and local-runtime follow-on owners".to_string(),
|
|
"current side-buffer trace shows separate infrastructure state and the direct seeded-lane bridge is now grounded before these later owners run".to_string(),
|
|
"direct disassembly now shows 0x00448a70 as a world-overlay byte write helper over [world+0x15e1/+0x162d], with the neighboring 0x00448af0 reading three world bitsets at [world+0x2139/+0x213d/+0x2141] rather than any infrastructure child fields".to_string(),
|
|
"direct disassembly now shows 0x00493660 as a counter-and-follow-on owner over one infrastructure child: it updates local counters by [child+0x218], [child+0x226], and [child+0x44], optionally resolves a peer through 0x48dcb0 and 0x426c20, then maps one world-raster byte back into the companion region collection 0x006cfc9c and calls 0x487960".to_string(),
|
|
"direct disassembly now shows 0x0048b660 as a presentation-color/style owner: it gates on global shell state, then branches on [child+0x216], [child+0x218], [child+0x226], [child+0x44], and bit 0x40 in [child+0x201] before publishing fixed RGBA tuples through 0x53a350".to_string(),
|
|
"the remaining local-runtime helpers are tighter now too: 0x0048e2c0 flips bit 0x20 in [child+0x201] and, when enabling that bit, reruns 0x53a3a0 plus 0x48a9e0; 0x0048e330 similarly flips bit 0x40 in [child+0x201] and tail-calls 0x48b660; and 0x0048e3c0 updates [child+0x22e] through the auxiliary route tracker 0x006cfcb4 after resolving one route entry via [child+0x212], then scans class-0 regions through 0x455800/0x455810 and 0x51dbb0 before setting bit 0x02 in [child+0x24c] only when the region test fails".to_string(),
|
|
],
|
|
blockers: vec![
|
|
"which mixed exact compact-prefix classes still survive into these later owners after the seeded-lane bridge and earlier child-stream restore semantics are accounted for".to_string(),
|
|
"no direct save-side correlation yet between the remaining mixed exact classes and the later 0x00493660 counter buckets, 0x0048b660 style branches, or the 0x0048e2c0/0x0048e3c0 flag-and-region follow-ons".to_string(),
|
|
],
|
|
},
|
|
];
|
|
let branches = vec![
|
|
build_service_trace_branch_status(
|
|
"infrastructure_asset_owner_seam",
|
|
if side_buffer.is_some() {
|
|
"grounded_separate_owner_seam"
|
|
} else {
|
|
"blocked_missing_side_buffer_owner_seam"
|
|
},
|
|
&[
|
|
"0x38a5/0x38a6/0x38a7 tagged family",
|
|
"embedded 0x55f1 dual-name rows",
|
|
"compact 6-byte prefix regimes",
|
|
],
|
|
if side_buffer.is_some() {
|
|
&[]
|
|
} else {
|
|
&["0x38a5 owner seam"]
|
|
},
|
|
&[
|
|
"0x00493be0 infrastructure tagged side-buffer collection load owner",
|
|
"0x0048dcf0 infrastructure tagged child-stream restore outer owner",
|
|
],
|
|
&[
|
|
"This seam should be treated as infrastructure-asset state rather than as a compact alias of placed-structure triplets.",
|
|
],
|
|
),
|
|
build_service_trace_branch_status(
|
|
"placed_structure_triplet_alias",
|
|
if alignment.is_some_and(|probe| probe.overlapping_name_pair_count == 0) {
|
|
"disproved_by_grounded_probe"
|
|
} else {
|
|
"unresolved"
|
|
},
|
|
&[
|
|
"0x36b1 placed-structure triplet corpus",
|
|
"0x38a5 side-buffer name-pair corpus",
|
|
],
|
|
&[],
|
|
&[],
|
|
&[
|
|
"Grounded q.gms evidence currently shows zero overlap between the side-buffer name-pair corpus and the placed-structure triplet name-pair corpus.",
|
|
],
|
|
),
|
|
build_service_trace_branch_status(
|
|
"city_connection_consumer_mapping",
|
|
"blocked_missing_infrastructure_asset_consumer_mapping",
|
|
&[
|
|
"grounded 0x38a5 owner seam",
|
|
"placed-structure triplet seam",
|
|
],
|
|
&[
|
|
"higher-layer consumer dispatch mapping",
|
|
"compact prefix regime semantics",
|
|
],
|
|
&[
|
|
"0x0048a1e0 infrastructure child attach helper",
|
|
"0x0048dd50 infrastructure child rebuild loop",
|
|
"0x00490a3c infrastructure payload attach helper",
|
|
],
|
|
&[
|
|
"The remaining problem is how higher-layer service code consumes this separate seam, not whether the seam exists.",
|
|
],
|
|
),
|
|
build_service_trace_branch_status(
|
|
"linked_transit_consumer_mapping",
|
|
"blocked_missing_infrastructure_asset_consumer_mapping",
|
|
&["grounded 0x38a5 owner seam", "company linked-transit latch"],
|
|
&[
|
|
"side-buffer consumer mapping",
|
|
"route or roster rebuild owner path",
|
|
],
|
|
&[
|
|
"0x00448a70 / 0x00493660 / 0x0048b660 route and world follow-on family",
|
|
"0x004133b0 placed-structure local-runtime refresh outer owner",
|
|
],
|
|
&[
|
|
"The next slice should target the consumer path above the side-buffer seam rather than another raw save scan.",
|
|
],
|
|
),
|
|
];
|
|
let notes = vec![
|
|
"Infrastructure asset trace now makes the side-buffer-versus-triplet split explicit: owner seam identity is grounded, the pure bridge-only 0x0002/0xff candidate class is grounded save-side, the upstream chooser above the child attach path is grounded as paired DT/ST siblings at 0x004a2c80 and 0x004a34e0 with decoded Bridge/Tunnel/BallastCap/Overpass families, grounded top-level branch meaning, grounded bridge/tunnel material selector roles, a concrete child-construction/write-side chain through 0x00490960, 0x00491c60, 0x0048a6c0, 0x00455a40, and 0x004559d0, and stable 0x00490960 mode families for BallastCap, TrackCap, Overpass, Tunnel, and Bridge branches. The current save-side name corpus already maps BallastCap, TrackCap, Tunnel, and Bridge rows onto those families directly, the candidate-pattern correlation narrows the dominant mixed 0x0001/0xff class to bridge:62 / track_cap:21 / tunnel:19, and the exact compact-prefix correlation now splits the full prelude corpus into mostly pure classes: 0xff0000ff/0x0002/0xff is pure bridge, 0xff000000/{0x0001,0x0002}/0xff are pure bridge, 0xf3010100/0x0055/0x00 is pure ballast-cap, and 0x0005d368/0x0001/0xff is pure track-cap, leaving only 0xff0000ff/0x0001/0xff and 0x000055f3/0x0001/0xff as the mixed residual classes.".to_string(),
|
|
"Cross-save q/p traces now also split those two mixed residual classes by footer and span behavior: 0x000055f3/0x0001/0xff always carries short-flag pair 0x01/0x00 on a fixed span-0x03 tunnel-dominant family, while 0xff0000ff/0x0001/0xff always carries short-flag pair 0x00/0x00 on the scattered-span TrackCap-dominant outlier family. The remaining unknown is therefore the meaning of those short-flag families and the sparse branch that routes a minority of tunnel rows into the 0xff0000ff outlier class.".to_string(),
|
|
"Direct consumers of those footer bits are grounded now too: bit 0x20 of [child+0x20] is the admission gate into the 0x00528d90 branch when no caller/global override is present, while bit 0x40 only feeds the later 0x00529730 -> 0x530280 follow-on. Since both mixed residual classes keep the second footer byte at zero in q/p, the remaining split is now specifically the first footer byte / bit-0x20 gate rather than both footer bytes.".to_string(),
|
|
"That bit-0x20 gate is no longer floating without context either: the 0x005295f0..0x005297b7 consumer strip repopulates candidate cells through 0x00533ba0, walks child lists through 0x00556ef0/0x00556f00, and honors the same controller mode byte [owner+0x3692] that the atlas already places under the world-window presentation dispatcher. The next infrastructure pass should therefore treat the remaining bit-0x20 question as a nearby-presentation/controller owner problem, not as a serializer-only problem.".to_string(),
|
|
"That owner family is layout-state specific now too: the surrounding helpers sit on atlas-backed layout/presenter roots, with 0x00548da0 walking layout list root [layout+0x2593] and 0x0054bab0 mutating layout slots [layout+0x2637/+0x263b/+0x2643]. So the remaining bit-0x20 split is increasingly a layout/presentation admission question above the infrastructure seam, not a simulation-owned route or rebuild question.".to_string(),
|
|
if st_only_name_pair_corpus {
|
|
"The current save-side side-buffer corpus is ST-only, so this trace directly exercises the ST chooser sibling while the DT sibling remains grounded statically but unexercised in this save.".to_string()
|
|
} else {
|
|
"The current save-side side-buffer corpus is not ST-only, so both chooser siblings or a DT-facing save-side class may be in play.".to_string()
|
|
},
|
|
];
|
|
SmpInfrastructureAssetTraceReport {
|
|
profile_family: analysis.profile_family.clone(),
|
|
placed_structure_collection_header_present: analysis
|
|
.placed_structure_collection_header
|
|
.is_some(),
|
|
placed_structure_record_triplet_count: analysis
|
|
.placed_structure_record_triplets
|
|
.as_ref()
|
|
.map(|probe| probe.record_count)
|
|
.unwrap_or_default(),
|
|
side_buffer_present: side_buffer.is_some(),
|
|
side_buffer_decoded_embedded_name_row_count: side_buffer
|
|
.map(|probe| probe.decoded_embedded_name_row_count)
|
|
.unwrap_or_default(),
|
|
side_buffer_unique_name_pair_count: side_buffer
|
|
.map(|probe| probe.unique_embedded_name_pair_count)
|
|
.unwrap_or_default(),
|
|
bridge_like_name_pair_count,
|
|
tunnel_like_name_pair_count,
|
|
track_cap_like_name_pair_count,
|
|
triplet_alignment_overlap_count: alignment
|
|
.map(|probe| probe.overlapping_name_pair_count)
|
|
.unwrap_or_default(),
|
|
atlas_candidate_consumers,
|
|
known_owner_bridge_fields,
|
|
known_bridge_helpers,
|
|
next_owner_questions,
|
|
candidate_consumer_hypotheses,
|
|
branches,
|
|
notes,
|
|
}
|
|
}
|
|
|
|
pub fn inspect_smp_bytes(bytes: &[u8]) -> SmpInspectionReport {
|
|
inspect_bundle_bytes(bytes, None)
|
|
}
|
|
|
|
fn derive_loaded_placed_structure_collection_from_probe(
|
|
probe: &SmpSavePlacedStructureRecordTripletProbe,
|
|
) -> SmpLoadedPlacedStructureCollection {
|
|
SmpLoadedPlacedStructureCollection {
|
|
source_kind: probe.source_kind.clone(),
|
|
semantic_family: "scenario-save-placed-structure-triplet-collection".to_string(),
|
|
observed_entry_count: probe.record_count,
|
|
entries: probe
|
|
.entries
|
|
.iter()
|
|
.map(|entry| SmpLoadedPlacedStructureEntry {
|
|
record_index: entry.record_index,
|
|
primary_name: entry.primary_name.clone(),
|
|
secondary_name: entry.secondary_name.clone(),
|
|
policy_trailing_word: entry.policy_trailing_word,
|
|
policy_trailing_word_hex: entry.policy_trailing_word_hex.clone(),
|
|
profile_payload_dword: entry.profile_payload_dword,
|
|
profile_payload_dword_hex: entry.profile_payload_dword_hex.clone(),
|
|
profile_status_kind: entry.profile_status_kind.clone(),
|
|
farm_growth_stage_index: entry.farm_growth_stage_index,
|
|
profile_companion_byte_u8: entry.profile_companion_byte_u8,
|
|
profile_companion_byte_hex: entry.profile_companion_byte_hex.clone(),
|
|
})
|
|
.collect(),
|
|
}
|
|
}
|
|
|
|
fn derive_loaded_region_collection_from_probe(
|
|
probe: &SmpSaveRegionRecordTripletProbe,
|
|
) -> SmpLoadedRegionCollection {
|
|
SmpLoadedRegionCollection {
|
|
source_kind: probe.source_kind.clone(),
|
|
semantic_family: "scenario-save-region-triplet-collection".to_string(),
|
|
observed_entry_count: probe.record_count,
|
|
entries: probe
|
|
.entries
|
|
.iter()
|
|
.map(|entry| SmpLoadedRegionEntry {
|
|
record_index: entry.record_index,
|
|
name: entry.name.clone(),
|
|
pre_name_prefix_len: entry.pre_name_prefix_len,
|
|
policy_leading_f32_0: entry.policy_leading_f32_0,
|
|
policy_leading_f32_1: entry.policy_leading_f32_1,
|
|
policy_leading_f32_2: entry.policy_leading_f32_2,
|
|
policy_reserved_dwords: entry.policy_reserved_dwords.clone(),
|
|
policy_trailing_word: entry.policy_trailing_word,
|
|
policy_trailing_word_hex: entry.policy_trailing_word_hex.clone(),
|
|
profile_collection: entry.profile_collection.as_ref().map(|collection| {
|
|
SmpLoadedRegionProfileCollection {
|
|
direct_collection_flag: collection.direct_collection_flag,
|
|
entry_stride: collection.entry_stride,
|
|
live_id_bound: collection.live_id_bound,
|
|
live_record_count: collection.live_record_count,
|
|
trailing_padding_len: collection.trailing_padding_len,
|
|
entries: collection
|
|
.entries
|
|
.iter()
|
|
.map(|entry| SmpLoadedRegionProfileEntry {
|
|
entry_index: entry.entry_index,
|
|
name: entry.name.clone(),
|
|
trailing_weight_f32: entry.trailing_weight_f32,
|
|
})
|
|
.collect(),
|
|
}
|
|
}),
|
|
})
|
|
.collect(),
|
|
}
|
|
}
|
|
|
|
fn derive_loaded_region_fixed_row_run_summary_from_probe(
|
|
probe: &SmpSaveRegionFixedRowRunCandidateProbe,
|
|
) -> SmpLoadedRegionFixedRowRunSummary {
|
|
SmpLoadedRegionFixedRowRunSummary {
|
|
source_kind: probe.source_kind.clone(),
|
|
semantic_family: "scenario-save-region-fixed-row-run-summary".to_string(),
|
|
target_row_count: probe.target_row_count,
|
|
target_row_stride: probe.target_row_stride,
|
|
target_row_stride_hex: probe.target_row_stride_hex.clone(),
|
|
candidates: probe.candidates.clone(),
|
|
}
|
|
}
|
|
|
|
fn derive_loaded_placed_structure_dynamic_side_buffer_summary(
|
|
probe: &SmpSavePlacedStructureDynamicSideBufferProbe,
|
|
alignment: Option<&SmpSavePlacedStructureDynamicSideBufferAlignmentProbe>,
|
|
) -> SmpLoadedPlacedStructureDynamicSideBufferSummary {
|
|
SmpLoadedPlacedStructureDynamicSideBufferSummary {
|
|
source_kind: probe.source_kind.clone(),
|
|
semantic_family: "scenario-save-placed-structure-dynamic-side-buffer-summary".to_string(),
|
|
observed_entry_count: probe.live_record_count,
|
|
owner_shared_dword_hex: probe.owner_shared_dword_hex.clone(),
|
|
unique_embedded_name_pair_count: probe.unique_embedded_name_pair_count,
|
|
decoded_embedded_name_row_count: probe.decoded_embedded_name_row_count,
|
|
first_prefix_leading_dword_hex: probe.prefix_leading_dword_hex.clone(),
|
|
first_prefix_trailing_word_hex: probe.prefix_trailing_word_hex.clone(),
|
|
first_prefix_separator_byte_hex: probe.prefix_separator_byte_hex.clone(),
|
|
triplet_alignment_overlap_count: alignment
|
|
.map(|alignment| alignment.overlapping_name_pair_count)
|
|
.unwrap_or_default(),
|
|
triplet_alignment_side_buffer_only_name_pair_count: alignment
|
|
.map(|alignment| {
|
|
alignment
|
|
.unique_side_buffer_name_pair_count
|
|
.saturating_sub(alignment.overlapping_name_pair_count)
|
|
})
|
|
.unwrap_or_default(),
|
|
compact_prefix_pattern_summaries: probe.compact_prefix_pattern_summaries.clone(),
|
|
name_pair_summaries: probe.name_pair_summaries.clone(),
|
|
}
|
|
}
|
|
|
|
pub fn load_save_slice_file(path: &Path) -> Result<SmpLoadedSaveSlice, Box<dyn std::error::Error>> {
|
|
let inspection = inspect_smp_file(path)?;
|
|
load_save_slice_from_report(&inspection)
|
|
.map_err(|err| -> Box<dyn std::error::Error> { err.into() })
|
|
}
|
|
|
|
pub fn load_save_slice_from_report(
|
|
report: &SmpInspectionReport,
|
|
) -> Result<SmpLoadedSaveSlice, String> {
|
|
let summary = report
|
|
.save_load_summary
|
|
.as_ref()
|
|
.ok_or_else(|| "inspection did not expose a recognizable save-load summary".to_string())?;
|
|
let profile = if let Some(probe) = &report.classic_rehydrate_profile_probe {
|
|
Some(SmpLoadedProfile {
|
|
profile_kind: "classic-rehydrate-profile".to_string(),
|
|
profile_family: probe.profile_family.clone(),
|
|
packed_profile_offset: probe.packed_profile_offset,
|
|
packed_profile_len: probe.packed_profile_len,
|
|
packed_profile_len_hex: probe.packed_profile_len_hex.clone(),
|
|
leading_word_0: probe.packed_profile_block.leading_word_0,
|
|
leading_word_0_hex: probe.packed_profile_block.leading_word_0_hex.clone(),
|
|
header_flag_word_3: None,
|
|
header_flag_word_3_hex: None,
|
|
map_path: probe.packed_profile_block.map_path.clone(),
|
|
display_name: probe.packed_profile_block.display_name.clone(),
|
|
profile_byte_0x77: probe.packed_profile_block.profile_byte_0x77,
|
|
profile_byte_0x77_hex: probe.packed_profile_block.profile_byte_0x77_hex.clone(),
|
|
profile_byte_0x82: probe.packed_profile_block.profile_byte_0x82,
|
|
profile_byte_0x82_hex: probe.packed_profile_block.profile_byte_0x82_hex.clone(),
|
|
profile_byte_0x97: probe.packed_profile_block.profile_byte_0x97,
|
|
profile_byte_0x97_hex: probe.packed_profile_block.profile_byte_0x97_hex.clone(),
|
|
profile_byte_0xc5: probe.packed_profile_block.profile_byte_0xc5,
|
|
profile_byte_0xc5_hex: probe.packed_profile_block.profile_byte_0xc5_hex.clone(),
|
|
})
|
|
} else {
|
|
report
|
|
.rt3_105_packed_profile_probe
|
|
.as_ref()
|
|
.map(|probe| SmpLoadedProfile {
|
|
profile_kind: "rt3-105-packed-profile".to_string(),
|
|
profile_family: probe.profile_family.clone(),
|
|
packed_profile_offset: probe.packed_profile_offset,
|
|
packed_profile_len: probe.packed_profile_len,
|
|
packed_profile_len_hex: probe.packed_profile_len_hex.clone(),
|
|
leading_word_0: probe.packed_profile_block.leading_word_0,
|
|
leading_word_0_hex: probe.packed_profile_block.leading_word_0_hex.clone(),
|
|
header_flag_word_3: Some(probe.packed_profile_block.header_flag_word_3),
|
|
header_flag_word_3_hex: Some(
|
|
probe.packed_profile_block.header_flag_word_3_hex.clone(),
|
|
),
|
|
map_path: probe.packed_profile_block.map_path.clone(),
|
|
display_name: probe.packed_profile_block.display_name.clone(),
|
|
profile_byte_0x77: probe.packed_profile_block.profile_byte_0x77,
|
|
profile_byte_0x77_hex: probe.packed_profile_block.profile_byte_0x77_hex.clone(),
|
|
profile_byte_0x82: probe.packed_profile_block.profile_byte_0x82,
|
|
profile_byte_0x82_hex: probe.packed_profile_block.profile_byte_0x82_hex.clone(),
|
|
profile_byte_0x97: probe.packed_profile_block.profile_byte_0x97,
|
|
profile_byte_0x97_hex: probe.packed_profile_block.profile_byte_0x97_hex.clone(),
|
|
profile_byte_0xc5: probe.packed_profile_block.profile_byte_0xc5,
|
|
profile_byte_0xc5_hex: probe.packed_profile_block.profile_byte_0xc5_hex.clone(),
|
|
})
|
|
};
|
|
let candidate_availability_table = report.rt3_105_save_name_table_probe.as_ref().map(|probe| {
|
|
SmpLoadedCandidateAvailabilityTable {
|
|
source_kind: probe.source_kind.clone(),
|
|
semantic_family: probe.semantic_family.clone(),
|
|
header_offset: probe.header_offset,
|
|
entries_offset: probe.entries_offset,
|
|
entries_end_offset: probe.entries_end_offset,
|
|
observed_entry_count: probe.observed_entry_count,
|
|
zero_availability_count: probe.zero_trailer_entry_count,
|
|
zero_availability_names: probe.zero_trailer_entry_names.clone(),
|
|
footer_progress_hex_words: vec![
|
|
probe.footer_progress_word_0_hex.clone(),
|
|
probe.footer_progress_word_1_hex.clone(),
|
|
],
|
|
entries: probe.entries.clone(),
|
|
}
|
|
});
|
|
let named_locomotive_availability_table = report
|
|
.rt3_105_save_named_locomotive_availability_probe
|
|
.as_ref()
|
|
.map(|probe| SmpLoadedNamedLocomotiveAvailabilityTable {
|
|
source_kind: probe.source_kind.clone(),
|
|
semantic_family: probe.semantic_family.clone(),
|
|
header_offset: None,
|
|
entries_offset: Some(probe.entries_offset),
|
|
entries_end_offset: Some(probe.entries_end_offset),
|
|
observed_entry_count: probe.observed_entry_count,
|
|
zero_availability_count: probe.zero_availability_count,
|
|
zero_availability_names: probe.zero_availability_names.clone(),
|
|
entries: probe.entries.clone(),
|
|
});
|
|
let locomotive_catalog = named_locomotive_availability_table
|
|
.as_ref()
|
|
.and_then(derive_locomotive_catalog_from_named_availability_table);
|
|
let cargo_catalog = report
|
|
.recipe_book_summary_probe
|
|
.as_ref()
|
|
.and_then(derive_cargo_catalog_from_recipe_book_probe);
|
|
let world_issue_37_state = report
|
|
.save_world_issue_37_probe
|
|
.as_ref()
|
|
.map(derive_loaded_world_issue_37_state_from_probe);
|
|
let world_economic_tuning_state = report
|
|
.save_world_economic_tuning_probe
|
|
.as_ref()
|
|
.map(derive_loaded_world_economic_tuning_state_from_probe);
|
|
let world_finance_neighborhood_state = report
|
|
.save_world_finance_neighborhood_probe
|
|
.as_ref()
|
|
.map(derive_loaded_world_finance_neighborhood_state_from_probe);
|
|
let world_locomotive_policy_state = derive_loaded_world_locomotive_policy_state_from_probes(
|
|
report.post_text_field_neighborhood_probe.as_ref(),
|
|
report.locomotive_policy_neighborhood_probe.as_ref(),
|
|
);
|
|
let company_roster = report.save_company_roster_probe.clone().or_else(|| {
|
|
report
|
|
.save_world_selection_context_probe
|
|
.as_ref()
|
|
.and_then(|probe| {
|
|
derive_selection_only_company_roster_from_save_world_probe(
|
|
probe,
|
|
report.save_company_collection_header_probe.as_ref(),
|
|
)
|
|
})
|
|
});
|
|
let chairman_profile_table = report
|
|
.save_chairman_profile_table_probe
|
|
.clone()
|
|
.or_else(|| {
|
|
report
|
|
.save_world_selection_context_probe
|
|
.as_ref()
|
|
.and_then(|probe| {
|
|
derive_selection_only_chairman_profile_table_from_save_world_probe(
|
|
probe,
|
|
report
|
|
.save_chairman_profile_collection_header_probe
|
|
.as_ref(),
|
|
)
|
|
})
|
|
});
|
|
let region_collection = report
|
|
.save_region_record_triplet_probe
|
|
.as_ref()
|
|
.map(derive_loaded_region_collection_from_probe);
|
|
let region_fixed_row_run_summary = report
|
|
.save_region_fixed_row_run_candidate_probe
|
|
.as_ref()
|
|
.map(derive_loaded_region_fixed_row_run_summary_from_probe);
|
|
let placed_structure_collection = report
|
|
.save_placed_structure_record_triplet_probe
|
|
.as_ref()
|
|
.map(derive_loaded_placed_structure_collection_from_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(),
|
|
});
|
|
let placed_structure_dynamic_side_buffer_probe = report
|
|
.save_placed_structure_dynamic_side_buffer_probe
|
|
.clone();
|
|
let placed_structure_dynamic_side_buffer_alignment = report
|
|
.save_placed_structure_dynamic_side_buffer_probe
|
|
.as_ref()
|
|
.zip(report.save_placed_structure_record_triplet_probe.as_ref())
|
|
.map(|(side_buffer, triplets)| {
|
|
summarize_placed_structure_dynamic_side_buffer_alignment(side_buffer, triplets)
|
|
});
|
|
let placed_structure_dynamic_side_buffer_summary = report
|
|
.save_placed_structure_dynamic_side_buffer_probe
|
|
.as_ref()
|
|
.map(|probe| {
|
|
derive_loaded_placed_structure_dynamic_side_buffer_summary(
|
|
probe,
|
|
placed_structure_dynamic_side_buffer_alignment.as_ref(),
|
|
)
|
|
});
|
|
let mut notes = summary.notes.clone();
|
|
if let Some(probe) = &report.save_world_selection_context_probe {
|
|
notes.push(format!(
|
|
"Raw save fixed world block exposes selected_company_id={} at file offset 0x{:x}.",
|
|
probe.selected_company_id, probe.selected_company_id_offset
|
|
));
|
|
notes.push(format!(
|
|
"Raw save fixed world block exposes selected_chairman_profile_id={} at file offset 0x{:x}.",
|
|
probe.selected_chairman_profile_id, probe.selected_chairman_profile_id_offset
|
|
));
|
|
notes.push(format!(
|
|
"Raw save fixed world block also exposes {} chairman slot selector bytes at file offset 0x{:x} and campaign_override_flag={} at file offset 0x{:x}.",
|
|
probe.chairman_slot_selectors.len(),
|
|
probe.chairman_slot_selector_offset,
|
|
probe.campaign_override_flag,
|
|
probe.campaign_override_flag_offset
|
|
));
|
|
if report.save_company_roster_probe.is_none()
|
|
|| report.save_chairman_profile_table_probe.is_none()
|
|
{
|
|
notes.push(
|
|
"Raw save inspection still does not reconstruct every company_roster or chairman_profile_table scalar lane; the grounded package-save path prefers direct-record reconstruction where it can and falls back to selection/header-only context otherwise."
|
|
.to_string(),
|
|
);
|
|
}
|
|
}
|
|
if let Some(probe) = &report.save_world_issue_37_probe {
|
|
notes.push(format!(
|
|
"Raw save fixed world block also exposes the grounded issue-0x37 pair: value={} at file offset 0x{:x} and companion multiplier {:.6} at file offset 0x{:x}.",
|
|
probe.issue_value_lane.value_i32,
|
|
probe.payload_offset + probe.issue_value_lane.relative_offset,
|
|
probe.multiplier_lane.value_f32,
|
|
probe.payload_offset + probe.multiplier_lane.relative_offset
|
|
));
|
|
}
|
|
if let Some(probe) = &report.save_world_economic_tuning_probe {
|
|
notes.push(format!(
|
|
"Raw save fixed world block also exposes the six-lane economic tuning float band at file offset 0x{:x} (mirror lane at 0x{:x}).",
|
|
probe.tuning_lanes
|
|
.first()
|
|
.map(|lane| probe.payload_offset + lane.relative_offset)
|
|
.unwrap_or(probe.payload_offset),
|
|
probe.payload_offset + probe.mirror_lane.relative_offset
|
|
));
|
|
notes.push(
|
|
"Current atlas evidence treats that fixed six-float world tuning band as the editor economic-cost family, not as the company-governance issue table behind investor confidence."
|
|
.to_string(),
|
|
);
|
|
}
|
|
if let Some(probe) = &report.save_company_collection_header_probe {
|
|
notes.push(format!(
|
|
"Raw save tagged company header reports live_record_count={} and live_id_bound={} at file offsets 0x{:x}/0x{:x}/0x{:x}.",
|
|
probe.live_record_count,
|
|
probe.live_id_bound,
|
|
probe.metadata_tag_offset,
|
|
probe.records_tag_offset,
|
|
probe.close_tag_offset
|
|
));
|
|
}
|
|
if let Some(probe) = &report.save_chairman_profile_collection_header_probe {
|
|
notes.push(format!(
|
|
"Raw save tagged chairman/profile header reports live_record_count={} and live_id_bound={} at file offsets 0x{:x}/0x{:x}/0x{:x}.",
|
|
probe.live_record_count,
|
|
probe.live_id_bound,
|
|
probe.metadata_tag_offset,
|
|
probe.records_tag_offset,
|
|
probe.close_tag_offset
|
|
));
|
|
}
|
|
if let Some(probe) = &report.save_train_collection_header_probe {
|
|
notes.push(format!(
|
|
"Raw save tagged train header reports live_record_count={} and live_id_bound={} with direct_record_stride=0x{:x} at file offsets 0x{:x}/0x{:x}/0x{:x}.",
|
|
probe.live_record_count,
|
|
probe.live_id_bound,
|
|
probe.direct_record_stride,
|
|
probe.metadata_tag_offset,
|
|
probe.records_tag_offset,
|
|
probe.close_tag_offset
|
|
));
|
|
}
|
|
if let Some(probe) = &report.save_train_collection_directory_probe {
|
|
notes.push(format!(
|
|
"Raw save tagged train metadata also exposes a live-entry directory with {} entries rooted at metadata dword {} (head={:?}, tail={:?}).",
|
|
probe.entries.len(),
|
|
probe.directory_root_dword_index,
|
|
probe.chain_head_live_entry_id,
|
|
probe.chain_tail_live_entry_id
|
|
));
|
|
}
|
|
if let Some(probe) = &report.save_region_collection_header_probe {
|
|
notes.push(format!(
|
|
"Raw save tagged region header reports live_record_count={} and live_id_bound={} with serialized stride hint 0x{:x} at file offsets 0x{:x}/0x{:x}/0x{:x}.",
|
|
probe.live_record_count,
|
|
probe.live_id_bound,
|
|
probe.direct_record_stride,
|
|
probe.metadata_tag_offset,
|
|
probe.records_tag_offset,
|
|
probe.close_tag_offset
|
|
));
|
|
}
|
|
if let Some(probe) = &report.save_region_record_triplet_probe {
|
|
notes.push(format!(
|
|
"Raw save tagged region records also expose {} repeated 0x55f1/0x55f2/0x55f3 triplets in the records span; first name={:?}, first policy lanes=({:.3}, {:.3}, {:.3}), trailing_word={}, first profile collection count={:?}, first profile collection trailing_padding_len={:?}.",
|
|
probe.record_count,
|
|
probe.entries.first().map(|entry| entry.name.as_str()),
|
|
probe.entries
|
|
.first()
|
|
.map(|entry| entry.policy_leading_f32_0)
|
|
.unwrap_or_default(),
|
|
probe.entries
|
|
.first()
|
|
.map(|entry| entry.policy_leading_f32_1)
|
|
.unwrap_or_default(),
|
|
probe.entries
|
|
.first()
|
|
.map(|entry| entry.policy_leading_f32_2)
|
|
.unwrap_or_default(),
|
|
probe.entries
|
|
.first()
|
|
.map(|entry| entry.policy_trailing_word_hex.as_str())
|
|
.unwrap_or("0x0000"),
|
|
probe.entries.first().and_then(|entry| {
|
|
entry.profile_collection.as_ref().map(|collection| collection.live_record_count)
|
|
}),
|
|
probe.entries.first().and_then(|entry| {
|
|
entry.profile_collection.as_ref().map(|collection| collection.trailing_padding_len)
|
|
})
|
|
));
|
|
}
|
|
if let Some(collection) = ®ion_collection {
|
|
let total_profile_rows = collection
|
|
.entries
|
|
.iter()
|
|
.map(|entry| {
|
|
entry
|
|
.profile_collection
|
|
.as_ref()
|
|
.map(|collection| collection.entries.len())
|
|
.unwrap_or_default()
|
|
})
|
|
.sum::<usize>();
|
|
let nonzero_prefix_count = collection
|
|
.entries
|
|
.iter()
|
|
.filter(|entry| entry.pre_name_prefix_len != 0)
|
|
.count();
|
|
let nonzero_reserved_count = collection
|
|
.entries
|
|
.iter()
|
|
.filter(|entry| entry.policy_reserved_dwords.iter().any(|raw| *raw != 0))
|
|
.count();
|
|
notes.push(format!(
|
|
"Save-slice projection now carries {} loaded region triplet rows as first-class context, with {} embedded profile rows, {} rows with nonzero pre-name prefixes, and {} rows with nonzero reserved policy dwords.",
|
|
collection.observed_entry_count,
|
|
total_profile_rows,
|
|
nonzero_prefix_count,
|
|
nonzero_reserved_count
|
|
));
|
|
}
|
|
if let Some(summary) = ®ion_fixed_row_run_summary {
|
|
notes.push(format!(
|
|
"Save-slice projection now also carries the region fixed-row run summary with {} candidate row bands at target stride {}, best rows offset {:?}, and best shape signature {:?}.",
|
|
summary.candidates.len(),
|
|
summary.target_row_stride_hex,
|
|
summary
|
|
.candidates
|
|
.first()
|
|
.map(|candidate| candidate.rows_offset_hex.as_str()),
|
|
summary
|
|
.candidates
|
|
.first()
|
|
.map(|candidate| candidate.shape_signature.as_str())
|
|
));
|
|
}
|
|
if let Some(probe) = &report.save_placed_structure_collection_header_probe {
|
|
notes.push(format!(
|
|
"Raw save tagged placed-structure header reports live_record_count={} and live_id_bound={} with serialized stride hint 0x{:x} at file offsets 0x{:x}/0x{:x}/0x{:x}.",
|
|
probe.live_record_count,
|
|
probe.live_id_bound,
|
|
probe.direct_record_stride,
|
|
probe.metadata_tag_offset,
|
|
probe.records_tag_offset,
|
|
probe.close_tag_offset
|
|
));
|
|
}
|
|
if let Some(probe) = &report.save_placed_structure_record_triplet_probe {
|
|
notes.push(format!(
|
|
"Raw save tagged placed-structure records also expose {} repeated 0x55f1/0x55f2/0x55f3 triplets; first stems={:?}/{:?}, first policy lanes=({:.3}, {:.3}, {:.3}, {:.3}, {:.3}), first footer payload={}, first footer status kind={:?}.",
|
|
probe.record_count,
|
|
probe.entries.first().map(|entry| entry.primary_name.as_str()),
|
|
probe.entries.first().map(|entry| entry.secondary_name.as_str()),
|
|
probe.entries.first().map(|entry| entry.policy_f32_lane_0).unwrap_or_default(),
|
|
probe.entries.first().map(|entry| entry.policy_f32_lane_1).unwrap_or_default(),
|
|
probe.entries.first().map(|entry| entry.policy_f32_lane_2).unwrap_or_default(),
|
|
probe.entries.first().map(|entry| entry.policy_f32_lane_3).unwrap_or_default(),
|
|
probe.entries.first().map(|entry| entry.policy_f32_lane_4).unwrap_or_default(),
|
|
probe.entries.first().map(|entry| entry.profile_payload_dword_hex.as_str()).unwrap_or("0x00000000"),
|
|
probe.entries.first().map(|entry| entry.profile_status_kind.as_str())
|
|
));
|
|
}
|
|
if let Some(collection) = &placed_structure_collection {
|
|
let farm_growth_stage_count = collection
|
|
.entries
|
|
.iter()
|
|
.filter(|entry| entry.farm_growth_stage_index.is_some())
|
|
.count();
|
|
let opaque_status_count = collection
|
|
.entries
|
|
.iter()
|
|
.filter(|entry| entry.profile_status_kind != "unset")
|
|
.count();
|
|
notes.push(format!(
|
|
"Save-slice projection now carries {} loaded placed-structure triplet rows as first-class context, with {} farm growth-stage rows and {} non-default footer-status rows.",
|
|
collection.observed_entry_count, farm_growth_stage_count, opaque_status_count
|
|
));
|
|
}
|
|
if let Some(probe) = &placed_structure_dynamic_side_buffer_probe {
|
|
let dominant_pattern = probe.compact_prefix_pattern_summaries.first();
|
|
let payload_envelope_summary = probe.payload_envelope_summary.as_ref();
|
|
let short_profile_flag_pair_summary = payload_envelope_summary
|
|
.and_then(|summary| summary.short_profile_flag_pair_summary.as_ref());
|
|
notes.push(format!(
|
|
"Raw save also exposes the separate placed-structure dynamic-side-buffer candidate 0x38a5/0x38a6/0x38a7: live_record_count={}, owner-shared 0x38a6 dword={} at relative offset 0x{:x}, first compact prefix=({},{},{}), first embedded names={:?}/{:?}/{:?}, embedded 0x55f1 row count={}, rows with tertiary 0x55f1 string={}, unique compact prefix patterns={}, 0x55f3-leading rows={}, complete 0x55f1/0x55f2/0x55f3 envelopes={}, dominant 0x55f2 chunk len=0x{:x} x{}, dominant 0x55f3 span=0x{:x} x{}, dominant short 0x55f3 flag pair={}/{} x{}, dominant compact pattern={}/{}/{} x{}.",
|
|
probe.live_record_count,
|
|
probe.owner_shared_dword_hex,
|
|
probe.owner_shared_dword_relative_offset,
|
|
probe.prefix_leading_dword_hex,
|
|
probe.prefix_trailing_word_hex,
|
|
probe.prefix_separator_byte_hex,
|
|
probe.first_embedded_primary_name.as_deref(),
|
|
probe.first_embedded_secondary_name.as_deref(),
|
|
probe.first_embedded_tertiary_name.as_deref(),
|
|
probe.embedded_name_tag_count,
|
|
probe.decoded_embedded_name_row_with_tertiary_name_count,
|
|
probe.unique_compact_prefix_pattern_count,
|
|
probe.prefix_leading_dword_matching_embedded_profile_tag_count,
|
|
payload_envelope_summary
|
|
.map(|summary| summary.row_count_with_complete_0x55f1_0x55f2_0x55f3_envelope)
|
|
.unwrap_or_default(),
|
|
payload_envelope_summary
|
|
.and_then(|summary| summary.dominant_policy_chunk_len)
|
|
.unwrap_or_default(),
|
|
payload_envelope_summary
|
|
.map(|summary| summary.dominant_policy_chunk_len_count)
|
|
.unwrap_or_default(),
|
|
payload_envelope_summary
|
|
.and_then(|summary| summary.dominant_profile_chunk_len)
|
|
.unwrap_or_default(),
|
|
payload_envelope_summary
|
|
.map(|summary| summary.dominant_profile_chunk_len_count)
|
|
.unwrap_or_default(),
|
|
short_profile_flag_pair_summary
|
|
.and_then(|summary| summary.dominant_flag_pair.as_ref())
|
|
.map(|pair| pair.first_flag_byte_hex.as_str())
|
|
.unwrap_or("0x00"),
|
|
short_profile_flag_pair_summary
|
|
.and_then(|summary| summary.dominant_flag_pair.as_ref())
|
|
.map(|pair| pair.second_flag_byte_hex.as_str())
|
|
.unwrap_or("0x00"),
|
|
short_profile_flag_pair_summary
|
|
.and_then(|summary| summary.dominant_flag_pair.as_ref())
|
|
.map(|pair| pair.count)
|
|
.unwrap_or_default(),
|
|
dominant_pattern
|
|
.map(|pattern| pattern.prefix_leading_dword_hex.as_str())
|
|
.unwrap_or("0x00000000"),
|
|
dominant_pattern
|
|
.map(|pattern| pattern.prefix_trailing_word_hex.as_str())
|
|
.unwrap_or("0x0000"),
|
|
dominant_pattern
|
|
.map(|pattern| pattern.prefix_separator_byte_hex.as_str())
|
|
.unwrap_or("0x00"),
|
|
dominant_pattern.map(|pattern| pattern.count).unwrap_or_default()
|
|
));
|
|
if probe.owner_shared_dword_matches_first_compact_prefix_leading_dword {
|
|
notes.push(
|
|
"Direct disassembly now shows 0x00493be0 consuming one shared 0x38a6 owner-local dword before iterating records; the first compact-prefix leading dword currently reuses that same lane."
|
|
.to_string(),
|
|
);
|
|
}
|
|
}
|
|
if let Some(summary) = &placed_structure_dynamic_side_buffer_summary {
|
|
notes.push(format!(
|
|
"Save-slice projection now also carries the placed-structure dynamic side-buffer summary with {} decoded name rows, {} unique name pairs, and {} overlapping triplet name pairs.",
|
|
summary.decoded_embedded_name_row_count,
|
|
summary.unique_embedded_name_pair_count,
|
|
summary.triplet_alignment_overlap_count
|
|
));
|
|
}
|
|
if let Some(roster) = &report.save_company_roster_probe {
|
|
notes.push(format!(
|
|
"Raw save inspection reconstructed {} company direct records from the tagged company collection.",
|
|
roster.entries.len()
|
|
));
|
|
}
|
|
if let Some(table) = &report.save_chairman_profile_table_probe {
|
|
notes.push(format!(
|
|
"Raw save inspection reconstructed {} chairman/profile direct records from the tagged chairman collection.",
|
|
table.entries.len()
|
|
));
|
|
}
|
|
|
|
Ok(SmpLoadedSaveSlice {
|
|
file_extension_hint: summary.file_extension_hint.clone(),
|
|
container_profile_family: summary.container_profile_family.clone(),
|
|
mechanism_family: summary.mechanism_family.clone(),
|
|
mechanism_confidence: summary.mechanism_confidence.clone(),
|
|
trailer_family: summary.trailer_family.clone(),
|
|
bridge_family: summary.bridge_family.clone(),
|
|
profile,
|
|
candidate_availability_table,
|
|
named_locomotive_availability_table,
|
|
locomotive_catalog,
|
|
cargo_catalog,
|
|
world_issue_37_state,
|
|
world_economic_tuning_state,
|
|
world_finance_neighborhood_state,
|
|
world_locomotive_policy_state,
|
|
company_roster,
|
|
chairman_profile_table,
|
|
region_collection,
|
|
region_fixed_row_run_summary,
|
|
placed_structure_collection,
|
|
placed_structure_dynamic_side_buffer_summary,
|
|
special_conditions_table,
|
|
event_runtime_collection: report.event_runtime_collection_summary.clone(),
|
|
notes,
|
|
})
|
|
}
|
|
|
|
pub fn inspect_save_company_and_chairman_analysis_file(
|
|
path: &Path,
|
|
) -> Result<SmpSaveCompanyChairmanAnalysisReport, Box<dyn std::error::Error>> {
|
|
let bytes = fs::read(path)?;
|
|
let report = inspect_bundle_bytes(
|
|
&bytes,
|
|
path.extension()
|
|
.and_then(|extension| extension.to_str())
|
|
.map(|extension| extension.to_ascii_lowercase()),
|
|
);
|
|
inspect_save_company_and_chairman_analysis_bytes(&bytes, &report)
|
|
.ok_or_else(|| "save inspection did not expose grounded company/chairman analysis".into())
|
|
}
|
|
|
|
pub fn inspect_save_company_and_chairman_analysis_bytes(
|
|
bytes: &[u8],
|
|
report: &SmpInspectionReport,
|
|
) -> Option<SmpSaveCompanyChairmanAnalysisReport> {
|
|
let selection_probe = report.save_world_selection_context_probe.as_ref();
|
|
let world_selection_context = selection_probe.map(build_save_world_selection_role_analysis);
|
|
let world_issue_37 = report.save_world_issue_37_probe.clone();
|
|
let world_economic_tuning = report.save_world_economic_tuning_probe.clone();
|
|
let world_finance_neighborhood = report.save_world_finance_neighborhood_probe.clone();
|
|
let train_collection_directory = report.save_train_collection_directory_probe.clone();
|
|
let region_record_triplets = report.save_region_record_triplet_probe.clone();
|
|
let region_queued_notice_records = report
|
|
.save_region_queued_notice_record_probe
|
|
.clone()
|
|
.or_else(|| {
|
|
parse_save_region_queued_notice_record_probe(
|
|
bytes,
|
|
report.file_extension_hint.as_deref(),
|
|
report.container_profile.as_ref(),
|
|
report.save_region_collection_header_probe.as_ref(),
|
|
)
|
|
});
|
|
let region_fixed_row_run_candidates = report
|
|
.save_region_fixed_row_run_candidate_probe
|
|
.clone()
|
|
.or_else(|| {
|
|
parse_save_region_fixed_row_run_candidate_probe(
|
|
bytes,
|
|
report.file_extension_hint.as_deref(),
|
|
report.container_profile.as_ref(),
|
|
report.save_region_collection_header_probe.as_ref(),
|
|
)
|
|
});
|
|
let placed_structure_record_triplets =
|
|
report.save_placed_structure_record_triplet_probe.clone();
|
|
let placed_structure_dynamic_side_buffer = report
|
|
.save_placed_structure_dynamic_side_buffer_probe
|
|
.clone()
|
|
.or_else(|| {
|
|
parse_save_placed_structure_dynamic_side_buffer_probe(
|
|
bytes,
|
|
report.file_extension_hint.as_deref(),
|
|
report.container_profile.as_ref(),
|
|
)
|
|
});
|
|
let placed_structure_dynamic_side_buffer_alignment = placed_structure_dynamic_side_buffer
|
|
.as_ref()
|
|
.zip(placed_structure_record_triplets.as_ref())
|
|
.map(|(side_buffer, triplets)| {
|
|
summarize_placed_structure_dynamic_side_buffer_alignment(side_buffer, triplets)
|
|
});
|
|
let unclassified_tagged_collection_headers = report
|
|
.save_unclassified_tagged_collection_header_probes
|
|
.clone();
|
|
let company_header_probe = report.save_company_collection_header_probe.as_ref();
|
|
let chairman_header_probe = report
|
|
.save_chairman_profile_collection_header_probe
|
|
.as_ref();
|
|
|
|
let company_entries = if let Some(header_probe) = company_header_probe {
|
|
let record_start_offset = detect_save_company_record_start_offset(&bytes, header_probe)?;
|
|
let record_stride = header_probe.direct_record_stride as usize;
|
|
let base_offset = header_probe
|
|
.metadata_tag_offset
|
|
.checked_add(4)?
|
|
.checked_add(record_start_offset)?;
|
|
let mut entries = Vec::with_capacity(header_probe.live_record_count as usize);
|
|
for index in 0..header_probe.live_record_count as usize {
|
|
let record_offset = base_offset.checked_add(index.checked_mul(record_stride)?)?;
|
|
let company_id = read_u32_at(&bytes, record_offset)?;
|
|
let name = read_ascii_c_string_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_NAME_OFFSET,
|
|
SAVE_COMPANY_RECORD_NAME_MAX_LEN,
|
|
)?;
|
|
let active =
|
|
read_u8_at(&bytes, record_offset + SAVE_COMPANY_RECORD_ACTIVE_OFFSET)? != 0;
|
|
let linked_chairman_profile_id = parse_nonzero_u32(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_LINKED_CHAIRMAN_OFFSET,
|
|
)?;
|
|
let outstanding_shares = read_u32_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_OUTSTANDING_SHARES_OFFSET,
|
|
)?;
|
|
let debt = parse_save_company_total_debt(&bytes, record_offset)?;
|
|
let bond_count = read_u8_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_BOND_COUNT_OFFSET,
|
|
)?;
|
|
let live_bond_slots = parse_save_company_live_bond_slots(&bytes, record_offset)?;
|
|
let largest_live_bond_principal =
|
|
parse_save_company_largest_live_bond_principal(&bytes, record_offset)?;
|
|
let highest_coupon_live_bond_principal =
|
|
parse_save_company_highest_coupon_live_bond_principal(&bytes, record_offset)?;
|
|
let available_track_laying_capacity =
|
|
parse_save_company_available_track_laying_capacity(&bytes, record_offset)?;
|
|
let company_value_scalar_f32 = read_f32_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_COMPANY_VALUE_OFFSET,
|
|
)?;
|
|
let cached_share_support_scalar_f32 = read_f32_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_SUPPORT_SCALAR_OFFSET,
|
|
)?;
|
|
let cached_share_price_f32 = read_f32_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_CACHED_SHARE_PRICE_OFFSET,
|
|
)?;
|
|
let chairman_salary_baseline = read_u32_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_BASELINE_OFFSET,
|
|
)?;
|
|
let chairman_salary_current = read_u32_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_CURRENT_OFFSET,
|
|
)?;
|
|
let chairman_bonus_year = read_u32_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_YEAR_OFFSET,
|
|
)?;
|
|
let chairman_bonus_amount = read_i32_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_AMOUNT_OFFSET,
|
|
)?;
|
|
let founding_year = read_u32_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_FOUNDING_YEAR_OFFSET,
|
|
)?;
|
|
let last_bankruptcy_year = read_u32_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_LAST_BANKRUPTCY_YEAR_OFFSET,
|
|
)?;
|
|
let last_dividend_year = read_u32_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_LAST_DIVIDEND_YEAR_OFFSET,
|
|
)?;
|
|
let preferred_locomotive_engine_type_raw_u8 = read_u8_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_PREFERRED_LOCOMOTIVE_ENGINE_TYPE_OFFSET,
|
|
)?;
|
|
let city_connection_latch = read_u8_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_CITY_CONNECTION_LATCH_OFFSET,
|
|
)? != 0;
|
|
let linked_transit_latch = read_u8_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_LINKED_TRANSIT_LATCH_OFFSET,
|
|
)? != 0;
|
|
let linked_transit_autoroute_site_score_cache_refresh_absolute_counter = read_u32_at(
|
|
&bytes,
|
|
record_offset
|
|
+ SAVE_COMPANY_RECORD_LINKED_TRANSIT_AUTOROUTE_SITE_SCORE_CACHE_REFRESH_ABSOLUTE_COUNTER_OFFSET,
|
|
)?;
|
|
let linked_transit_site_peer_cache_refresh_absolute_counter = read_u32_at(
|
|
&bytes,
|
|
record_offset
|
|
+ SAVE_COMPANY_RECORD_LINKED_TRANSIT_SITE_PEER_CACHE_REFRESH_ABSOLUTE_COUNTER_OFFSET,
|
|
)?;
|
|
let linked_transit_route_anchor_entry_id = parse_nonzero_u32(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_LINKED_TRANSIT_ROUTE_ANCHOR_ENTRY_ID_OFFSET,
|
|
)?;
|
|
let linked_transit_route_anchor_fallback_counts =
|
|
SAVE_COMPANY_RECORD_LINKED_TRANSIT_ROUTE_ANCHOR_FALLBACK_COUNT_OFFSETS
|
|
.iter()
|
|
.map(|relative_offset| read_u32_at(&bytes, record_offset + *relative_offset))
|
|
.collect::<Option<Vec<_>>>()?;
|
|
let merger_cooldown_year = read_u32_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_MERGER_COOLDOWN_OFFSET,
|
|
)?;
|
|
let takeover_cooldown_year = read_u32_at(
|
|
&bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_TAKEOVER_COOLDOWN_OFFSET,
|
|
)?;
|
|
let scalar_dword_candidates = SAVE_COMPANY_RECORD_SCALAR_CANDIDATE_FIELDS
|
|
.iter()
|
|
.map(|(label, relative_offset)| {
|
|
build_save_dword_candidate(&bytes, record_offset, label, *relative_offset)
|
|
})
|
|
.collect::<Option<Vec<_>>>()?;
|
|
let post_capacity_dword_candidates = SAVE_COMPANY_RECORD_POST_CAPACITY_CANDIDATE_FIELDS
|
|
.iter()
|
|
.map(|(label, relative_offset)| {
|
|
build_save_dword_candidate(&bytes, record_offset, label, *relative_offset)
|
|
})
|
|
.collect::<Option<Vec<_>>>()?;
|
|
let stat_band_root_0cfb_candidates = build_save_company_stat_band_candidates(
|
|
&bytes,
|
|
record_offset,
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET,
|
|
"stat_band_0cfb",
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS,
|
|
)?;
|
|
let stat_band_root_0d7f_candidates = build_save_company_stat_band_candidates(
|
|
&bytes,
|
|
record_offset,
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET,
|
|
"stat_band_0d7f",
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS,
|
|
)?;
|
|
let stat_band_root_1c47_candidates = build_save_company_stat_band_candidates(
|
|
&bytes,
|
|
record_offset,
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET,
|
|
"stat_band_1c47",
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS,
|
|
)?;
|
|
entries.push(SmpSaveCompanyRecordAnalysisEntry {
|
|
company_id,
|
|
name,
|
|
active,
|
|
linked_chairman_profile_id,
|
|
outstanding_shares,
|
|
debt,
|
|
bond_count,
|
|
live_bond_slots,
|
|
largest_live_bond_principal,
|
|
highest_coupon_live_bond_principal,
|
|
available_track_laying_capacity,
|
|
company_value_scalar_f32,
|
|
cached_share_support_scalar_f32,
|
|
cached_share_price_f32,
|
|
chairman_salary_baseline,
|
|
chairman_salary_current,
|
|
chairman_bonus_year,
|
|
chairman_bonus_amount,
|
|
founding_year,
|
|
last_bankruptcy_year,
|
|
last_dividend_year,
|
|
preferred_locomotive_engine_type_raw_u8,
|
|
preferred_locomotive_engine_type_raw_hex: format!(
|
|
"0x{preferred_locomotive_engine_type_raw_u8:02x}"
|
|
),
|
|
city_connection_latch,
|
|
linked_transit_latch,
|
|
linked_transit_autoroute_site_score_cache_refresh_absolute_counter,
|
|
linked_transit_site_peer_cache_refresh_absolute_counter,
|
|
linked_transit_route_anchor_entry_id,
|
|
linked_transit_route_anchor_fallback_counts,
|
|
merger_cooldown_year,
|
|
takeover_cooldown_year,
|
|
scalar_dword_candidates,
|
|
post_capacity_dword_candidates,
|
|
stat_band_root_0cfb_candidates,
|
|
stat_band_root_0d7f_candidates,
|
|
stat_band_root_1c47_candidates,
|
|
});
|
|
}
|
|
entries
|
|
} else {
|
|
Vec::new()
|
|
};
|
|
let company_share_prices = company_entries
|
|
.iter()
|
|
.filter_map(|entry| {
|
|
round_f64_to_i64(entry.cached_share_price_f32 as f64)
|
|
.map(|share_price| (entry.company_id, share_price))
|
|
})
|
|
.collect::<BTreeMap<_, _>>();
|
|
|
|
let chairman_entries = if let Some(header_probe) = chairman_header_probe {
|
|
let record_start_offset =
|
|
detect_save_chairman_profile_record_start_offset(&bytes, header_probe)?;
|
|
let record_stride = header_probe.direct_record_stride as usize;
|
|
let base_offset = header_probe
|
|
.metadata_tag_offset
|
|
.checked_add(4)?
|
|
.checked_add(record_start_offset)?;
|
|
let company_id_bound = company_header_probe
|
|
.map(|probe| probe.live_id_bound)
|
|
.unwrap_or(0);
|
|
let mut entries = Vec::with_capacity(header_probe.live_record_count as usize);
|
|
for index in 0..header_probe.live_record_count as usize {
|
|
let record_offset = base_offset.checked_add(index.checked_mul(record_stride)?)?;
|
|
let profile_id = read_u32_at(&bytes, record_offset)?;
|
|
let active = read_u32_at(&bytes, record_offset + 4)? != 0;
|
|
let name = read_ascii_c_string_at(
|
|
&bytes,
|
|
record_offset + SAVE_CHAIRMAN_RECORD_NAME_OFFSET,
|
|
SAVE_CHAIRMAN_RECORD_NAME_MAX_LEN,
|
|
)?;
|
|
let current_cash =
|
|
read_f64_at(&bytes, record_offset + SAVE_CHAIRMAN_RECORD_CASH_OFFSET)?;
|
|
let linked_company_id = parse_nonzero_u32(
|
|
&bytes,
|
|
record_offset + SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET,
|
|
)?;
|
|
let personality_byte_0x291 = read_u8_at(
|
|
&bytes,
|
|
record_offset + SAVE_CHAIRMAN_RECORD_PERSONALITY_BYTE_0X291_OFFSET,
|
|
)?;
|
|
let mut holdings_by_company = BTreeMap::new();
|
|
for company_id in 1..=company_id_bound {
|
|
let slot_offset = record_offset
|
|
.checked_add(SAVE_CHAIRMAN_RECORD_HOLDINGS_BASE_OFFSET)?
|
|
.checked_add((company_id as usize).checked_mul(4)?)?;
|
|
let units = read_u32_at(&bytes, slot_offset)?;
|
|
if units != 0 {
|
|
holdings_by_company.insert(company_id, units);
|
|
}
|
|
}
|
|
let cached_scalar_candidates = SAVE_CHAIRMAN_RECORD_CACHE_CANDIDATE_OFFSETS
|
|
.iter()
|
|
.map(|relative_offset| {
|
|
build_save_qword_candidate(&bytes, record_offset, *relative_offset)
|
|
})
|
|
.collect::<Option<Vec<_>>>()?;
|
|
let rounded_current_cash = round_f64_to_i64(current_cash)?;
|
|
let derived_holdings_share_price_total = derive_chairman_holdings_share_price_total(
|
|
&holdings_by_company,
|
|
&company_share_prices,
|
|
);
|
|
let derived_net_worth_share_price_total = derived_holdings_share_price_total
|
|
.and_then(|holdings_total| rounded_current_cash.checked_add(holdings_total));
|
|
let derived_cached_purchasing_power_total =
|
|
derive_chairman_cached_purchasing_power_total(
|
|
rounded_current_cash,
|
|
&cached_scalar_candidates,
|
|
);
|
|
entries.push(SmpSaveChairmanRecordAnalysisEntry {
|
|
profile_id,
|
|
name,
|
|
active,
|
|
current_cash,
|
|
linked_company_id,
|
|
holdings_by_company,
|
|
derived_holdings_share_price_total,
|
|
derived_net_worth_share_price_total,
|
|
derived_cached_purchasing_power_total,
|
|
personality_byte_0x291,
|
|
personality_byte_0x291_hex: format!("0x{personality_byte_0x291:02x}"),
|
|
cached_scalar_candidates,
|
|
});
|
|
}
|
|
entries
|
|
} else {
|
|
Vec::new()
|
|
};
|
|
|
|
let mut notes = Vec::new();
|
|
if world_selection_context.is_some() {
|
|
notes.push(
|
|
"World selection context now exports the grounded chairman-slot selector bytes and per-slot role-gate bytes from the fixed save-side 0x32c8 world block."
|
|
.to_string(),
|
|
);
|
|
}
|
|
if world_issue_37.is_some() {
|
|
notes.push(
|
|
"World analysis now also exports the grounded issue-0x37 pair from the same 0x32c8 world payload: the clamped small issue value at [world+0x2d] and its companion multiplier lane at [world+0x29]."
|
|
.to_string(),
|
|
);
|
|
}
|
|
if world_economic_tuning.is_some() {
|
|
notes.push(
|
|
"World analysis now also exports the fixed six-lane economic tuning float block from the same 0x32c8 world payload; current atlas evidence still treats that band as distinct from the issue-0x37 investor-confidence family."
|
|
.to_string(),
|
|
);
|
|
}
|
|
if world_finance_neighborhood.is_some() {
|
|
notes.push(
|
|
"World analysis now also exports one fixed dword finance neighborhood around the grounded issue/calendar lanes, so future issue-0x38/0x39 closure can build on rehosted owner-state candidates instead of ad hoc byte guesses."
|
|
.to_string(),
|
|
);
|
|
}
|
|
if let Some(header) = report.save_train_collection_header_probe.as_ref() {
|
|
notes.push(format!(
|
|
"Train analysis now also exports the tagged train collection header: live_record_count={} live_id_bound={} direct_record_stride=0x{:x}.",
|
|
header.live_record_count, header.live_id_bound, header.direct_record_stride
|
|
));
|
|
}
|
|
if let Some(directory) = train_collection_directory.as_ref() {
|
|
notes.push(format!(
|
|
"Train analysis now also exports the tagged live-entry directory rooted at metadata dword {}: {} entries chained from {:?} to {:?}.",
|
|
directory.directory_root_dword_index,
|
|
directory.entries.len(),
|
|
directory.chain_head_live_entry_id,
|
|
directory.chain_tail_live_entry_id
|
|
));
|
|
}
|
|
if let Some(header) = report.save_region_collection_header_probe.as_ref() {
|
|
notes.push(format!(
|
|
"Region analysis now also exports the non-direct tagged region collection header: live_record_count={} live_id_bound={} serialized_stride_hint=0x{:x}.",
|
|
header.live_record_count, header.live_id_bound, header.direct_record_stride
|
|
));
|
|
}
|
|
if let Some(triplets) = region_record_triplets.as_ref() {
|
|
notes.push(format!(
|
|
"Region analysis now also exports {} tagged 0x55f1/0x55f2/0x55f3 record triplets; first serialized region name={:?}, first policy lanes=({:.3}, {:.3}, {:.3}), first profile collection count={:?}, first profile collection trailing_padding_len={:?}.",
|
|
triplets.record_count,
|
|
triplets.entries.first().map(|entry| entry.name.as_str()),
|
|
triplets
|
|
.entries
|
|
.first()
|
|
.map(|entry| entry.policy_leading_f32_0)
|
|
.unwrap_or_default(),
|
|
triplets
|
|
.entries
|
|
.first()
|
|
.map(|entry| entry.policy_leading_f32_1)
|
|
.unwrap_or_default(),
|
|
triplets
|
|
.entries
|
|
.first()
|
|
.map(|entry| entry.policy_leading_f32_2)
|
|
.unwrap_or_default(),
|
|
triplets.entries.first().and_then(|entry| {
|
|
entry.profile_collection.as_ref().map(|collection| collection.live_record_count)
|
|
}),
|
|
triplets.entries.first().and_then(|entry| {
|
|
entry.profile_collection.as_ref().map(|collection| collection.trailing_padding_len)
|
|
})
|
|
));
|
|
}
|
|
if let Some(queue_probe) = region_queued_notice_records.as_ref() {
|
|
notes.push(format!(
|
|
"Region analysis now also exports {} queued kind-7 notice nodes with payload seed {}: first region id={} amount={} promotion={} tails={}/{}.",
|
|
queue_probe.entries.len(),
|
|
queue_probe.payload_seed_dword_hex,
|
|
queue_probe.entries[0].region_id,
|
|
queue_probe.entries[0].amount,
|
|
queue_probe.entries[0].promotion_latch_dword_hex,
|
|
queue_probe.entries[0].trailing_sentinel_i32_0_hex,
|
|
queue_probe.entries[0].trailing_sentinel_i32_1_hex
|
|
));
|
|
}
|
|
if let Some(fixed_row_candidates) = region_fixed_row_run_candidates.as_ref() {
|
|
notes.push(format!(
|
|
"Region analysis now also exports {} fixed-row run candidates keyed to live_record_count={} and stride {} before the tagged region metadata; best candidate rows offset is {:?} with shape signature {:?}.",
|
|
fixed_row_candidates.candidates.len(),
|
|
fixed_row_candidates.target_row_count,
|
|
fixed_row_candidates.target_row_stride_hex,
|
|
fixed_row_candidates
|
|
.candidates
|
|
.first()
|
|
.map(|candidate| candidate.rows_offset_hex.as_str()),
|
|
fixed_row_candidates
|
|
.candidates
|
|
.first()
|
|
.map(|candidate| candidate.shape_signature.as_str())
|
|
));
|
|
}
|
|
if let Some(header) = report
|
|
.save_placed_structure_collection_header_probe
|
|
.as_ref()
|
|
{
|
|
notes.push(format!(
|
|
"Placed-structure analysis now also exports the tagged collection header: live_record_count={} live_id_bound={} serialized_stride_hint=0x{:x}.",
|
|
header.live_record_count, header.live_id_bound, header.direct_record_stride
|
|
));
|
|
}
|
|
if let Some(triplets) = placed_structure_record_triplets.as_ref() {
|
|
notes.push(format!(
|
|
"Placed-structure analysis now also exports {} tagged 0x55f1/0x55f2/0x55f3 record triplets; first stems={:?}/{:?}, first footer payload={}, first footer status kind={:?}.",
|
|
triplets.record_count,
|
|
triplets.entries.first().map(|entry| entry.primary_name.as_str()),
|
|
triplets.entries.first().map(|entry| entry.secondary_name.as_str()),
|
|
triplets.entries.first().map(|entry| entry.profile_payload_dword_hex.as_str()).unwrap_or("0x00000000"),
|
|
triplets.entries.first().map(|entry| entry.profile_status_kind.as_str())
|
|
));
|
|
}
|
|
if let Some(side_buffer) = placed_structure_dynamic_side_buffer.as_ref() {
|
|
let dominant_pattern = side_buffer.compact_prefix_pattern_summaries.first();
|
|
let payload_envelope_summary = side_buffer.payload_envelope_summary.as_ref();
|
|
let short_profile_flag_pair_summary = payload_envelope_summary
|
|
.and_then(|summary| summary.short_profile_flag_pair_summary.as_ref());
|
|
notes.push(format!(
|
|
"Placed-structure analysis now also exports the separate 0x38a5 dynamic side-buffer owner seam with {} embedded name rows, {} decoded rows across {} unique name pairs, {} rows with a tertiary 0x55f1 string, {} unique compact prefix patterns, {} rows whose leading dword matches 0x55f3, {} complete 0x55f1/0x55f2/0x55f3 envelopes, dominant 0x55f2 chunk len=0x{:x} x{}, dominant 0x55f3 span=0x{:x} x{}, dominant short 0x55f3 flag pair={}/{} x{}, and dominant compact pattern={}/{}/{} x{}.",
|
|
side_buffer.embedded_name_tag_count,
|
|
side_buffer.decoded_embedded_name_row_count,
|
|
side_buffer.unique_embedded_name_pair_count,
|
|
side_buffer.decoded_embedded_name_row_with_tertiary_name_count,
|
|
side_buffer.unique_compact_prefix_pattern_count,
|
|
side_buffer.prefix_leading_dword_matching_embedded_profile_tag_count,
|
|
payload_envelope_summary
|
|
.map(|summary| summary.row_count_with_complete_0x55f1_0x55f2_0x55f3_envelope)
|
|
.unwrap_or_default(),
|
|
payload_envelope_summary
|
|
.and_then(|summary| summary.dominant_policy_chunk_len)
|
|
.unwrap_or_default(),
|
|
payload_envelope_summary
|
|
.map(|summary| summary.dominant_policy_chunk_len_count)
|
|
.unwrap_or_default(),
|
|
payload_envelope_summary
|
|
.and_then(|summary| summary.dominant_profile_chunk_len)
|
|
.unwrap_or_default(),
|
|
payload_envelope_summary
|
|
.map(|summary| summary.dominant_profile_chunk_len_count)
|
|
.unwrap_or_default(),
|
|
short_profile_flag_pair_summary
|
|
.and_then(|summary| summary.dominant_flag_pair.as_ref())
|
|
.map(|pair| pair.first_flag_byte_hex.as_str())
|
|
.unwrap_or("0x00"),
|
|
short_profile_flag_pair_summary
|
|
.and_then(|summary| summary.dominant_flag_pair.as_ref())
|
|
.map(|pair| pair.second_flag_byte_hex.as_str())
|
|
.unwrap_or("0x00"),
|
|
short_profile_flag_pair_summary
|
|
.and_then(|summary| summary.dominant_flag_pair.as_ref())
|
|
.map(|pair| pair.count)
|
|
.unwrap_or_default(),
|
|
dominant_pattern
|
|
.map(|pattern| pattern.prefix_leading_dword_hex.as_str())
|
|
.unwrap_or("0x00000000"),
|
|
dominant_pattern
|
|
.map(|pattern| pattern.prefix_trailing_word_hex.as_str())
|
|
.unwrap_or("0x0000"),
|
|
dominant_pattern
|
|
.map(|pattern| pattern.prefix_separator_byte_hex.as_str())
|
|
.unwrap_or("0x00"),
|
|
dominant_pattern.map(|pattern| pattern.count).unwrap_or_default()
|
|
));
|
|
}
|
|
if let Some(alignment) = placed_structure_dynamic_side_buffer_alignment.as_ref() {
|
|
notes.push(format!(
|
|
"Placed-structure analysis now also compares the 0x38a5 side-buffer against the grounded 0x36b1 triplet corpus: {} of {} decoded side-buffer rows reuse {} overlapping placed-structure name pairs, leaving {} unmatched side-buffer rows and {} triplet-only name pairs.",
|
|
alignment.side_buffer_rows_with_matching_triplet_name_pair_count,
|
|
alignment.side_buffer_row_count,
|
|
alignment.overlapping_name_pair_count,
|
|
alignment.side_buffer_rows_without_matching_triplet_name_pair_count,
|
|
alignment.triplet_name_pairs_without_side_buffer_match_count
|
|
));
|
|
}
|
|
if let Some(candidate) = unclassified_tagged_collection_headers.first() {
|
|
notes.push(format!(
|
|
"Generic save-side tagged collection scan also found {} unclassified candidate families; largest current candidate uses tags {}/{}/{} with live_record_count={} stride=0x{:x} records_span_len=0x{:x}.",
|
|
unclassified_tagged_collection_headers.len(),
|
|
candidate.metadata_tag_hex,
|
|
candidate.records_tag_hex,
|
|
candidate.close_tag_hex,
|
|
candidate.live_record_count,
|
|
candidate.direct_record_stride,
|
|
candidate.records_span_len
|
|
));
|
|
}
|
|
if !company_entries.is_empty() {
|
|
notes.push(
|
|
"Company debt is derived from the grounded bond table at [company+0x5b/+0x5f] by summing live principal slots.".to_string(),
|
|
);
|
|
notes.push(
|
|
"Company available_track_laying_capacity is derived from the grounded tail dword [company+0x7680], with negative values treated as the unlimited sentinel.".to_string(),
|
|
);
|
|
notes.push(
|
|
"Company scalar_dword_candidates expose the current checked-in raw save windows around support/share-price/calendar lanes, and post_capacity_dword_candidates expose the immediate dwords after [company+0x7680] for deeper track-count and record-tail analysis.".to_string(),
|
|
);
|
|
notes.push(
|
|
"Company stat-band root candidates now also expose the first dword windows rooted at [company+0x0cfb], [company+0x0d7f], and [company+0x1c47], the same broader stat bands the grounded cheat reset branch clears before later finance/detail readers rebuild them.".to_string(),
|
|
);
|
|
notes.push(
|
|
"Current atlas evidence ties company current_cash and book_value_per_share to stat-family 0x2329 slots 0x0d and 0x1d, so the remaining save-native company finance/governance closure likely needs a structured company-stat family reconstruction instead of more isolated raw offsets."
|
|
.to_string(),
|
|
);
|
|
}
|
|
if !chairman_entries.is_empty() {
|
|
notes.push(
|
|
"Chairman cached_scalar_candidates expose the adjacent qword band rooted at [profile+0x1e9], now including raw qword hex and signed/f64 views for further purchasing-power analysis.".to_string(),
|
|
);
|
|
notes.push(
|
|
"Chairman analysis now also derives one holdings-at-cached-share-price total from the grounded company cached_share_price lane and one strongest-cached purchasing-power total from the nonnegative qword cache band."
|
|
.to_string(),
|
|
);
|
|
}
|
|
|
|
Some(SmpSaveCompanyChairmanAnalysisReport {
|
|
profile_family: report
|
|
.container_profile
|
|
.as_ref()
|
|
.map(|profile| profile.profile_family.clone())
|
|
.unwrap_or_else(|| "unknown".to_string()),
|
|
selected_company_id: selection_probe.map(|probe| probe.selected_company_id),
|
|
selected_chairman_profile_id: selection_probe
|
|
.map(|probe| probe.selected_chairman_profile_id),
|
|
world_selection_context,
|
|
world_issue_37,
|
|
world_economic_tuning,
|
|
world_finance_neighborhood,
|
|
train_collection_header: report.save_train_collection_header_probe.clone(),
|
|
train_collection_directory,
|
|
region_collection_header: report.save_region_collection_header_probe.clone(),
|
|
region_record_triplets,
|
|
region_queued_notice_records,
|
|
region_fixed_row_run_candidates,
|
|
placed_structure_collection_header: report
|
|
.save_placed_structure_collection_header_probe
|
|
.clone(),
|
|
placed_structure_record_triplets,
|
|
placed_structure_dynamic_side_buffer,
|
|
placed_structure_dynamic_side_buffer_alignment,
|
|
unclassified_tagged_collection_headers,
|
|
company_entries,
|
|
chairman_entries,
|
|
notes,
|
|
})
|
|
}
|
|
|
|
pub fn compare_save_region_fixed_row_run_candidates(
|
|
left: &SmpSaveCompanyChairmanAnalysisReport,
|
|
right: &SmpSaveCompanyChairmanAnalysisReport,
|
|
) -> Option<SmpSaveRegionFixedRowRunComparisonReport> {
|
|
let left_probe = left.region_fixed_row_run_candidates.as_ref()?;
|
|
let right_probe = right.region_fixed_row_run_candidates.as_ref()?;
|
|
|
|
let left_by_shape = left_probe
|
|
.candidates
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(index, candidate)| (candidate.shape_signature.clone(), (index, candidate)))
|
|
.collect::<BTreeMap<_, _>>();
|
|
let right_by_shape = right_probe
|
|
.candidates
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(index, candidate)| (candidate.shape_signature.clone(), (index, candidate)))
|
|
.collect::<BTreeMap<_, _>>();
|
|
|
|
let mut shared_shape_matches = Vec::new();
|
|
let mut shared_shape_family_matches = Vec::new();
|
|
let mut left_only_shape_signatures = Vec::new();
|
|
let mut right_only_shape_signatures = Vec::new();
|
|
let mut left_only_shape_family_signatures = Vec::new();
|
|
let mut right_only_shape_family_signatures = Vec::new();
|
|
let left_family_by_shape = left_probe
|
|
.candidates
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(index, candidate)| (candidate.shape_family_signature.clone(), (index, candidate)))
|
|
.collect::<BTreeMap<_, _>>();
|
|
let right_family_by_shape = right_probe
|
|
.candidates
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(index, candidate)| (candidate.shape_family_signature.clone(), (index, candidate)))
|
|
.collect::<BTreeMap<_, _>>();
|
|
|
|
for (shape_signature, (left_index, left_candidate)) in &left_by_shape {
|
|
if let Some((right_index, right_candidate)) = right_by_shape.get(shape_signature) {
|
|
shared_shape_matches.push(SmpSaveRegionFixedRowRunSharedShapeMatch {
|
|
shape_signature: shape_signature.clone(),
|
|
left_rank: left_index + 1,
|
|
left_rows_offset_hex: left_candidate.rows_offset_hex.clone(),
|
|
left_best_probable_density_lane_relative_offset_hex: left_candidate
|
|
.best_probable_density_lane_relative_offset_hex
|
|
.clone(),
|
|
right_rank: right_index + 1,
|
|
right_rows_offset_hex: right_candidate.rows_offset_hex.clone(),
|
|
right_best_probable_density_lane_relative_offset_hex: right_candidate
|
|
.best_probable_density_lane_relative_offset_hex
|
|
.clone(),
|
|
});
|
|
} else {
|
|
left_only_shape_signatures.push(shape_signature.clone());
|
|
}
|
|
}
|
|
|
|
for shape_signature in right_by_shape.keys() {
|
|
if !left_by_shape.contains_key(shape_signature) {
|
|
right_only_shape_signatures.push(shape_signature.clone());
|
|
}
|
|
}
|
|
|
|
for (shape_family_signature, (left_index, left_candidate)) in &left_family_by_shape {
|
|
if let Some((right_index, right_candidate)) =
|
|
right_family_by_shape.get(shape_family_signature)
|
|
{
|
|
shared_shape_family_matches.push(SmpSaveRegionFixedRowRunSharedShapeMatch {
|
|
shape_signature: shape_family_signature.clone(),
|
|
left_rank: left_index + 1,
|
|
left_rows_offset_hex: left_candidate.rows_offset_hex.clone(),
|
|
left_best_probable_density_lane_relative_offset_hex: left_candidate
|
|
.best_probable_density_lane_relative_offset_hex
|
|
.clone(),
|
|
right_rank: right_index + 1,
|
|
right_rows_offset_hex: right_candidate.rows_offset_hex.clone(),
|
|
right_best_probable_density_lane_relative_offset_hex: right_candidate
|
|
.best_probable_density_lane_relative_offset_hex
|
|
.clone(),
|
|
});
|
|
} else {
|
|
left_only_shape_family_signatures.push(shape_family_signature.clone());
|
|
}
|
|
}
|
|
|
|
for shape_family_signature in right_family_by_shape.keys() {
|
|
if !left_family_by_shape.contains_key(shape_family_signature) {
|
|
right_only_shape_family_signatures.push(shape_family_signature.clone());
|
|
}
|
|
}
|
|
|
|
Some(SmpSaveRegionFixedRowRunComparisonReport {
|
|
left_profile_family: left.profile_family.clone(),
|
|
right_profile_family: right.profile_family.clone(),
|
|
left_best_rows_offset_hex: left_probe
|
|
.candidates
|
|
.first()
|
|
.map(|candidate| candidate.rows_offset_hex.clone()),
|
|
right_best_rows_offset_hex: right_probe
|
|
.candidates
|
|
.first()
|
|
.map(|candidate| candidate.rows_offset_hex.clone()),
|
|
left_best_shape_signature: left_probe
|
|
.candidates
|
|
.first()
|
|
.map(|candidate| candidate.shape_signature.clone()),
|
|
right_best_shape_signature: right_probe
|
|
.candidates
|
|
.first()
|
|
.map(|candidate| candidate.shape_signature.clone()),
|
|
left_best_shape_family_signature: left_probe
|
|
.candidates
|
|
.first()
|
|
.map(|candidate| candidate.shape_family_signature.clone()),
|
|
right_best_shape_family_signature: right_probe
|
|
.candidates
|
|
.first()
|
|
.map(|candidate| candidate.shape_family_signature.clone()),
|
|
shared_shape_matches,
|
|
shared_shape_family_matches,
|
|
left_only_shape_signatures,
|
|
right_only_shape_signatures,
|
|
left_only_shape_family_signatures,
|
|
right_only_shape_family_signatures,
|
|
evidence: vec![
|
|
format!(
|
|
"comparison keys the pre-region-header fixed-row candidates by derived lane-shape fingerprint instead of raw offset, because current grounded saves do not keep the same top rows_offset across files ({:?} vs {:?})",
|
|
left_probe.candidates.first().map(|candidate| candidate.rows_offset_hex.as_str()),
|
|
right_probe.candidates.first().map(|candidate| candidate.rows_offset_hex.as_str())
|
|
),
|
|
"shared shape matches mean two saves surfaced at least one candidate with the same exact probable-f32/small-unsigned/partial-zero/trailing-byte profile, while shared shape-family matches allow mild count drift inside the same dense lane family".to_string(),
|
|
],
|
|
})
|
|
}
|
|
|
|
fn derive_locomotive_catalog_from_named_availability_table(
|
|
table: &SmpLoadedNamedLocomotiveAvailabilityTable,
|
|
) -> Option<SmpLoadedLocomotiveCatalog> {
|
|
if table.entries.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let entries = table
|
|
.entries
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(index, entry)| SmpLoadedLocomotiveCatalogEntry {
|
|
locomotive_id: (index + 1) as u32,
|
|
name: entry.text.clone(),
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
Some(SmpLoadedLocomotiveCatalog {
|
|
source_kind: format!("{}-ordinal-catalog", table.source_kind),
|
|
semantic_family: "scenario-save-derived-locomotive-catalog".to_string(),
|
|
entries_offset: table.entries_offset,
|
|
observed_entry_count: entries.len(),
|
|
entries,
|
|
})
|
|
}
|
|
|
|
fn derive_cargo_catalog_from_recipe_book_probe(
|
|
probe: &SmpRecipeBookSummaryProbe,
|
|
) -> Option<SmpLoadedCargoCatalog> {
|
|
if probe.books.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let entries = probe
|
|
.books
|
|
.iter()
|
|
.filter(|book| book.book_index < 11)
|
|
.filter_map(|book| {
|
|
let line = book
|
|
.lines
|
|
.iter()
|
|
.find(|line| line.imports_to_runtime_descriptor)
|
|
.or_else(|| book.lines.first())?;
|
|
let slot_id = (book.book_index + 1) as u32;
|
|
let definition = known_cargo_slot_definition(slot_id)?;
|
|
Some(SmpLoadedCargoCatalogEntry {
|
|
slot_id,
|
|
label: definition.label.to_string(),
|
|
cargo_class: definition.cargo_class,
|
|
book_index: book.book_index,
|
|
max_annual_production_word: book.max_annual_production_word,
|
|
mode_word: line.mode_word,
|
|
runtime_import_branch_kind: line.runtime_import_branch_kind.clone(),
|
|
annual_amount_word: line.annual_amount_word,
|
|
supplied_cargo_token_word: line.supplied_cargo_token_word,
|
|
supplied_cargo_token_probable_high16_ascii_stem: line
|
|
.supplied_cargo_token_probable_high16_ascii_stem
|
|
.clone(),
|
|
demanded_cargo_token_word: line.demanded_cargo_token_word,
|
|
demanded_cargo_token_probable_high16_ascii_stem: line
|
|
.demanded_cargo_token_probable_high16_ascii_stem
|
|
.clone(),
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
if entries.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
Some(SmpLoadedCargoCatalog {
|
|
source_kind: format!("{}-slot-catalog", probe.source_kind),
|
|
semantic_family: "scenario-save-derived-cargo-catalog".to_string(),
|
|
root_offset: Some(probe.root_offset),
|
|
observed_entry_count: entries.len(),
|
|
entries,
|
|
})
|
|
}
|
|
|
|
fn derive_loaded_world_issue_37_state_from_probe(
|
|
probe: &SmpSaveWorldIssue37Probe,
|
|
) -> SmpLoadedWorldIssue37State {
|
|
SmpLoadedWorldIssue37State {
|
|
source_kind: probe.source_kind.clone(),
|
|
semantic_family: probe.semantic_family.clone(),
|
|
issue_value: probe.issue_value_lane.raw_u32,
|
|
issue_value_hex: probe.issue_value_lane.raw_u32_hex.clone(),
|
|
issue_38_value: u32::from(probe.issue_38_raw_u8),
|
|
issue_38_value_hex: probe.issue_38_raw_hex.clone(),
|
|
issue_39_value: u32::from(probe.issue_39_raw_u8),
|
|
issue_39_value_hex: probe.issue_39_raw_hex.clone(),
|
|
issue_3a_value: u32::from(probe.issue_3a_raw_u8),
|
|
issue_3a_value_hex: probe.issue_3a_raw_hex.clone(),
|
|
multiplier_raw_u32: probe.multiplier_lane.raw_u32,
|
|
multiplier_raw_hex: probe.multiplier_lane.raw_u32_hex.clone(),
|
|
multiplier_value_f32_text: format!("{:.6}", probe.multiplier_lane.value_f32),
|
|
issue_opinion_base_terms_raw_i32: probe.issue_opinion_base_terms_raw_i32.clone(),
|
|
}
|
|
}
|
|
|
|
fn derive_loaded_world_economic_tuning_state_from_probe(
|
|
probe: &SmpSaveWorldEconomicTuningProbe,
|
|
) -> SmpLoadedWorldEconomicTuningState {
|
|
SmpLoadedWorldEconomicTuningState {
|
|
source_kind: probe.source_kind.clone(),
|
|
semantic_family: probe.semantic_family.clone(),
|
|
mirror_raw_u32: probe.mirror_lane.raw_u32,
|
|
mirror_raw_hex: probe.mirror_lane.raw_u32_hex.clone(),
|
|
mirror_value_f32_text: format!("{:.6}", probe.mirror_lane.value_f32),
|
|
lane_raw_u32: probe.tuning_lanes.iter().map(|lane| lane.raw_u32).collect(),
|
|
lane_raw_hex: probe
|
|
.tuning_lanes
|
|
.iter()
|
|
.map(|lane| lane.raw_u32_hex.clone())
|
|
.collect(),
|
|
lane_value_f32_text: probe
|
|
.tuning_lanes
|
|
.iter()
|
|
.map(|lane| format!("{:.6}", lane.value_f32))
|
|
.collect(),
|
|
}
|
|
}
|
|
|
|
fn derive_loaded_world_finance_neighborhood_state_from_probe(
|
|
probe: &SmpSaveWorldFinanceNeighborhoodProbe,
|
|
) -> SmpLoadedWorldFinanceNeighborhoodState {
|
|
SmpLoadedWorldFinanceNeighborhoodState {
|
|
source_kind: probe.source_kind.clone(),
|
|
semantic_family: probe.semantic_family.clone(),
|
|
packed_year_word_raw_u16: probe.packed_year_word_raw_u16,
|
|
packed_year_word_raw_hex: probe.packed_year_word_raw_hex.clone(),
|
|
partial_year_progress_raw_u8: probe.partial_year_progress_raw_u8,
|
|
partial_year_progress_raw_hex: probe.partial_year_progress_raw_hex.clone(),
|
|
current_calendar_tuple_word_raw_u32: probe.current_calendar_tuple_word_lane.raw_u32,
|
|
current_calendar_tuple_word_raw_hex: probe
|
|
.current_calendar_tuple_word_lane
|
|
.raw_u32_hex
|
|
.clone(),
|
|
current_calendar_tuple_word_2_raw_u32: probe.current_calendar_tuple_word_2_lane.raw_u32,
|
|
current_calendar_tuple_word_2_raw_hex: probe
|
|
.current_calendar_tuple_word_2_lane
|
|
.raw_u32_hex
|
|
.clone(),
|
|
absolute_counter_raw_u32: probe.absolute_counter_lane.raw_u32,
|
|
absolute_counter_raw_hex: probe.absolute_counter_lane.raw_u32_hex.clone(),
|
|
absolute_counter_mirror_raw_u32: probe.absolute_counter_mirror_lane.raw_u32,
|
|
absolute_counter_mirror_raw_hex: probe.absolute_counter_mirror_lane.raw_u32_hex.clone(),
|
|
stock_policy_raw_u8: probe.stock_policy_raw_u8,
|
|
stock_policy_raw_hex: probe.stock_policy_raw_hex.clone(),
|
|
bond_policy_raw_u8: probe.bond_policy_raw_u8,
|
|
bond_policy_raw_hex: probe.bond_policy_raw_hex.clone(),
|
|
bankruptcy_policy_raw_u8: probe.bankruptcy_policy_raw_u8,
|
|
bankruptcy_policy_raw_hex: probe.bankruptcy_policy_raw_hex.clone(),
|
|
dividend_policy_raw_u8: probe.dividend_policy_raw_u8,
|
|
dividend_policy_raw_hex: probe.dividend_policy_raw_hex.clone(),
|
|
building_density_growth_setting_raw_u32: probe.building_density_growth_setting_lane.raw_u32,
|
|
building_density_growth_setting_raw_hex: probe
|
|
.building_density_growth_setting_lane
|
|
.raw_u32_hex
|
|
.clone(),
|
|
labels: probe
|
|
.dword_candidates
|
|
.iter()
|
|
.map(|candidate| candidate.label.clone())
|
|
.collect(),
|
|
relative_offsets: probe
|
|
.dword_candidates
|
|
.iter()
|
|
.map(|candidate| candidate.relative_offset)
|
|
.collect(),
|
|
relative_offset_hex: probe
|
|
.dword_candidates
|
|
.iter()
|
|
.map(|candidate| candidate.relative_offset_hex.clone())
|
|
.collect(),
|
|
raw_u32: probe
|
|
.dword_candidates
|
|
.iter()
|
|
.map(|candidate| candidate.raw_u32)
|
|
.collect(),
|
|
raw_hex: probe
|
|
.dword_candidates
|
|
.iter()
|
|
.map(|candidate| candidate.raw_u32_hex.clone())
|
|
.collect(),
|
|
value_i32: probe
|
|
.dword_candidates
|
|
.iter()
|
|
.map(|candidate| candidate.value_i32)
|
|
.collect(),
|
|
value_f32_text: probe
|
|
.dword_candidates
|
|
.iter()
|
|
.map(|candidate| format!("{:.6}", candidate.value_f32))
|
|
.collect(),
|
|
}
|
|
}
|
|
|
|
fn derive_loaded_world_locomotive_policy_state_from_probes(
|
|
post_text_probe: Option<&SmpPostTextFieldNeighborhoodProbe>,
|
|
locomotive_policy_probe: Option<&SmpLocomotivePolicyNeighborhoodProbe>,
|
|
) -> Option<SmpLoadedWorldLocomotivePolicyState> {
|
|
let field_by_name = |name: &str| {
|
|
locomotive_policy_probe?
|
|
.grounded_field_observations
|
|
.iter()
|
|
.find(|field| field.field_name == name)
|
|
};
|
|
let post_text_field_by_name = |name: &str| {
|
|
post_text_probe?
|
|
.grounded_field_observations
|
|
.iter()
|
|
.find(|field| field.field_name == name)
|
|
};
|
|
let selected_year_gap_scalar = field_by_name("selected-year bucket companion scalar");
|
|
let linked_site_gate = field_by_name("linked-site removal follow-on gate");
|
|
let auto_show_grade = post_text_field_by_name("Auto-Show Grade During Track Lay");
|
|
let starting_building_density = post_text_field_by_name("Starting Building Density Level");
|
|
let building_density_growth = post_text_field_by_name("Building Density Growth");
|
|
let leftover_simulation_time = post_text_field_by_name("leftover simulation time accumulator");
|
|
let selected_year_snapshot = post_text_field_by_name("selected-year lane snapshot");
|
|
let all_steam = field_by_name("All Steam Locos Avail.");
|
|
let all_diesel = field_by_name("All Diesel Locos Avail.");
|
|
let all_electric = field_by_name("All Electric Locos Avail.");
|
|
let cached_available_rating = field_by_name("cached available-locomotive rating");
|
|
Some(SmpLoadedWorldLocomotivePolicyState {
|
|
source_kind: locomotive_policy_probe
|
|
.map(|probe| probe.source_kind.clone())
|
|
.or_else(|| post_text_probe.map(|probe| probe.source_kind.clone()))?,
|
|
semantic_family: "world-locomotive-policy".to_string(),
|
|
selected_year_gap_scalar_raw_u32: selected_year_gap_scalar
|
|
.and_then(|field| field.value_u32),
|
|
selected_year_gap_scalar_raw_hex: selected_year_gap_scalar
|
|
.and_then(|field| field.value_u32_hex.clone()),
|
|
selected_year_gap_scalar_value_f32_text: selected_year_gap_scalar
|
|
.and_then(|field| field.probable_f32_le.clone()),
|
|
linked_site_removal_follow_on_gate_raw_u8: linked_site_gate
|
|
.and_then(|field| field.value_u8),
|
|
linked_site_removal_follow_on_gate_raw_hex: linked_site_gate
|
|
.and_then(|field| field.value_u8_hex.clone()),
|
|
auto_show_grade_during_track_lay_raw_u8: auto_show_grade.and_then(|field| field.value_u8),
|
|
auto_show_grade_during_track_lay_raw_hex: auto_show_grade
|
|
.and_then(|field| field.value_u8_hex.clone()),
|
|
starting_building_density_level_raw_u8: starting_building_density
|
|
.and_then(|field| field.value_u8),
|
|
starting_building_density_level_raw_hex: starting_building_density
|
|
.and_then(|field| field.value_u8_hex.clone()),
|
|
building_density_growth_raw_u8: building_density_growth.and_then(|field| field.value_u8),
|
|
building_density_growth_raw_hex: building_density_growth
|
|
.and_then(|field| field.value_u8_hex.clone()),
|
|
leftover_simulation_time_accumulator_raw_u32: leftover_simulation_time
|
|
.and_then(|field| field.value_u32),
|
|
leftover_simulation_time_accumulator_raw_hex: leftover_simulation_time
|
|
.and_then(|field| field.value_u32_hex.clone()),
|
|
leftover_simulation_time_accumulator_value_f32_text: leftover_simulation_time
|
|
.and_then(|field| field.probable_f32_le.clone()),
|
|
selected_year_lane_snapshot_raw_u8: selected_year_snapshot.and_then(|field| field.value_u8),
|
|
selected_year_lane_snapshot_raw_hex: selected_year_snapshot
|
|
.and_then(|field| field.value_u8_hex.clone()),
|
|
all_steam_locomotives_available_raw_u8: all_steam.and_then(|field| field.value_u8),
|
|
all_steam_locomotives_available_raw_hex: all_steam
|
|
.and_then(|field| field.value_u8_hex.clone()),
|
|
all_diesel_locomotives_available_raw_u8: all_diesel.and_then(|field| field.value_u8),
|
|
all_diesel_locomotives_available_raw_hex: all_diesel
|
|
.and_then(|field| field.value_u8_hex.clone()),
|
|
all_electric_locomotives_available_raw_u8: all_electric.and_then(|field| field.value_u8),
|
|
all_electric_locomotives_available_raw_hex: all_electric
|
|
.and_then(|field| field.value_u8_hex.clone()),
|
|
cached_available_locomotive_rating_raw_u32: cached_available_rating
|
|
.and_then(|field| field.value_u32),
|
|
cached_available_locomotive_rating_raw_hex: cached_available_rating
|
|
.and_then(|field| field.value_u32_hex.clone()),
|
|
cached_available_locomotive_rating_value_f32_text: cached_available_rating
|
|
.and_then(|field| field.probable_f32_le.clone()),
|
|
})
|
|
}
|
|
|
|
fn derive_selection_only_company_roster_from_save_world_probe(
|
|
probe: &SmpSaveWorldSelectionContextProbe,
|
|
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
|
) -> Option<SmpLoadedCompanyRoster> {
|
|
Some(SmpLoadedCompanyRoster {
|
|
source_kind: format!("{}-company-selection-only", probe.source_kind),
|
|
semantic_family: "scenario-selected-company-context".to_string(),
|
|
observed_entry_count: header_probe
|
|
.map(|probe| probe.live_record_count as usize)
|
|
.unwrap_or(0),
|
|
selected_company_id: Some(probe.selected_company_id),
|
|
entries: Vec::new(),
|
|
})
|
|
}
|
|
|
|
fn derive_selection_only_chairman_profile_table_from_save_world_probe(
|
|
probe: &SmpSaveWorldSelectionContextProbe,
|
|
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
|
) -> Option<SmpLoadedChairmanProfileTable> {
|
|
Some(SmpLoadedChairmanProfileTable {
|
|
source_kind: format!("{}-chairman-selection-only", probe.source_kind),
|
|
semantic_family: "scenario-selected-chairman-context".to_string(),
|
|
observed_entry_count: header_probe
|
|
.map(|probe| probe.live_record_count as usize)
|
|
.unwrap_or(0),
|
|
selected_chairman_profile_id: Some(probe.selected_chairman_profile_id),
|
|
entries: Vec::new(),
|
|
})
|
|
}
|
|
|
|
const SAVE_COMPANY_RECORD_NAME_OFFSET: usize = 0x04;
|
|
const SAVE_COMPANY_RECORD_NAME_MAX_LEN: usize = 0x24;
|
|
const SAVE_COMPANY_RECORD_LINKED_CHAIRMAN_OFFSET: usize = 0x3b;
|
|
const SAVE_COMPANY_RECORD_ACTIVE_OFFSET: usize = 0x3f;
|
|
const SAVE_COMPANY_RECORD_OUTSTANDING_SHARES_OFFSET: usize = 0x47;
|
|
const SAVE_COMPANY_RECORD_SUPPORT_SCALAR_OFFSET: usize = 0x4f;
|
|
const SAVE_COMPANY_RECORD_COMPANY_VALUE_OFFSET: usize = 0x57;
|
|
const SAVE_COMPANY_RECORD_BOND_COUNT_OFFSET: usize = 0x5b;
|
|
const SAVE_COMPANY_RECORD_BOND_TABLE_OFFSET: usize = 0x5f;
|
|
const SAVE_COMPANY_RECORD_BOND_SLOT_STRIDE: usize = 12;
|
|
const SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_BASELINE_OFFSET: usize = 0x14f;
|
|
const SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_YEAR_OFFSET: usize = 0x34f;
|
|
const SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_AMOUNT_OFFSET: usize = 0x353;
|
|
const SAVE_COMPANY_RECORD_MERGER_COOLDOWN_OFFSET: usize = 0x15f;
|
|
const SAVE_COMPANY_RECORD_FOUNDING_YEAR_OFFSET: usize = 0x157;
|
|
const SAVE_COMPANY_RECORD_LAST_BANKRUPTCY_YEAR_OFFSET: usize = 0x163;
|
|
const SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET: usize = 0x16b;
|
|
const SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET_2: usize = 0x16f;
|
|
const SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET: usize = 0x173;
|
|
const SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET_2: usize = 0x177;
|
|
const SAVE_COMPANY_RECORD_TAKEOVER_COOLDOWN_OFFSET: usize = 0x289;
|
|
const SAVE_COMPANY_RECORD_LAST_DIVIDEND_YEAR_OFFSET: usize = 0x0d2d;
|
|
const SAVE_COMPANY_RECORD_PREFERRED_LOCOMOTIVE_ENGINE_TYPE_OFFSET: usize = 0x0d17;
|
|
const SAVE_COMPANY_RECORD_CITY_CONNECTION_LATCH_OFFSET: usize = 0x0d18;
|
|
const SAVE_COMPANY_RECORD_LINKED_TRANSIT_ROUTE_ANCHOR_ENTRY_ID_OFFSET: usize = 0x0d35;
|
|
const SAVE_COMPANY_RECORD_LINKED_TRANSIT_AUTOROUTE_SITE_SCORE_CACHE_REFRESH_ABSOLUTE_COUNTER_OFFSET: usize =
|
|
0x0d3a;
|
|
const SAVE_COMPANY_RECORD_LINKED_TRANSIT_SITE_PEER_CACHE_REFRESH_ABSOLUTE_COUNTER_OFFSET: usize =
|
|
0x0d3e;
|
|
const SAVE_COMPANY_RECORD_SUPPORT_PROGRESS_OFFSET: usize = 0x0d07;
|
|
const SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_CURRENT_OFFSET: usize = 0x0d59;
|
|
const SAVE_COMPANY_RECORD_LINKED_TRANSIT_LATCH_OFFSET: usize = 0x0d56;
|
|
const SAVE_COMPANY_RECORD_RECENT_PER_SHARE_SUBSCORE_OFFSET: usize = 0x0d19;
|
|
const SAVE_COMPANY_RECORD_CACHED_SHARE_PRICE_OFFSET: usize = 0x0d7b;
|
|
const SAVE_COMPANY_RECORD_TRACK_LAYING_CAPACITY_OFFSET: usize = 0x7680;
|
|
const SAVE_COMPANY_RECORD_LINKED_TRANSIT_ROUTE_ANCHOR_FALLBACK_COUNT_OFFSETS: [usize; 3] =
|
|
[0x7664, 0x7668, 0x766c];
|
|
const SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET: usize = 0x0cfb;
|
|
const SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET: usize = 0x0d7f;
|
|
const SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET: usize = 0x1c47;
|
|
const SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS: usize = 32;
|
|
const SAVE_COMPANY_RECORD_ISSUE_OPINION_TERMS_OFFSET: usize = 0x2ab;
|
|
const SAVE_COMPANY_RECORD_ISSUE_OPINION_TERM_COUNT: usize =
|
|
RT3_SAVE_WORLD_BLOCK_ISSUE_OPINION_TERM_COUNT;
|
|
const SAVE_COMPANY_RECORD_YEAR_STAT_FAMILY_QWORD_COUNT: usize =
|
|
crate::runtime::RUNTIME_COMPANY_STAT_SLOT_COUNT as usize
|
|
* crate::runtime::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN as usize;
|
|
const SAVE_COMPANY_RECORD_SPECIAL_STAT_FAMILY_232A_QWORD_COUNT: usize =
|
|
crate::runtime::RUNTIME_COMPANY_STAT_SLOT_COUNT as usize;
|
|
const SAVE_COMPANY_RECORD_DIRECT_CONTROL_TRANSFER_FLOAT_FIELDS: [usize; 10] = [
|
|
0x4b, 0x53, 0x323, 0x327, 0x32b, 0x32f, 0x333, 0x337, 0x33b, 0x33f,
|
|
];
|
|
const SAVE_COMPANY_RECORD_DIRECT_CONTROL_TRANSFER_INT_FIELDS: [usize; 5] =
|
|
[0x14f, 0x34b, 0x0d0b, 0x0d0f, 0x0d13];
|
|
const SAVE_COMPANY_RECORD_SCALAR_CANDIDATE_FIELDS: [(&str, usize); 9] = [
|
|
("mutable_support_scalar", 0x4f),
|
|
("young_company_support_scalar", 0x57),
|
|
(
|
|
"support_progress_word",
|
|
SAVE_COMPANY_RECORD_SUPPORT_PROGRESS_OFFSET,
|
|
),
|
|
(
|
|
"recent_per_share_subscore",
|
|
SAVE_COMPANY_RECORD_RECENT_PER_SHARE_SUBSCORE_OFFSET,
|
|
),
|
|
("cached_share_price", 0x0d7b),
|
|
(
|
|
"current_issue_calendar_word",
|
|
SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET,
|
|
),
|
|
(
|
|
"current_issue_calendar_word_2",
|
|
SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET_2,
|
|
),
|
|
(
|
|
"prior_issue_calendar_word",
|
|
SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET,
|
|
),
|
|
(
|
|
"prior_issue_calendar_word_2",
|
|
SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET_2,
|
|
),
|
|
];
|
|
const SAVE_COMPANY_RECORD_POST_CAPACITY_CANDIDATE_FIELDS: [(&str, usize); 6] = [
|
|
("post_capacity_word_1", 0x7684),
|
|
("post_capacity_word_2", 0x7688),
|
|
("post_capacity_word_3", 0x768c),
|
|
("post_capacity_word_4", 0x7690),
|
|
("post_capacity_word_5", 0x7694),
|
|
("post_capacity_word_6", 0x7698),
|
|
];
|
|
const SAVE_COMPANY_RECORD_START_SCAN_LIMIT: usize = 0x120;
|
|
|
|
const SAVE_CHAIRMAN_RECORD_NAME_OFFSET: usize = 0x08;
|
|
const SAVE_CHAIRMAN_RECORD_NAME_MAX_LEN: usize = 0x1f;
|
|
const SAVE_CHAIRMAN_RECORD_CASH_OFFSET: usize = 0x154;
|
|
const SAVE_CHAIRMAN_RECORD_HOLDINGS_BASE_OFFSET: usize = 0x15d;
|
|
const SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET: usize = 0x1dd;
|
|
const SAVE_CHAIRMAN_RECORD_CACHE_0_OFFSET: usize = 0x1e9;
|
|
const SAVE_CHAIRMAN_RECORD_CACHE_1_OFFSET: usize = 0x1f1;
|
|
const SAVE_CHAIRMAN_RECORD_PERSONALITY_BYTE_0X291_OFFSET: usize = 0x291;
|
|
const SAVE_CHAIRMAN_RECORD_ISSUE_OPINION_TERMS_OFFSET: usize = 0x35b;
|
|
const SAVE_CHAIRMAN_RECORD_ISSUE_OPINION_TERM_COUNT: usize =
|
|
RT3_SAVE_WORLD_BLOCK_ISSUE_OPINION_TERM_COUNT;
|
|
const SAVE_CHAIRMAN_RECORD_CACHE_CANDIDATE_OFFSETS: [usize; 7] =
|
|
[0x1e9, 0x1f1, 0x1f9, 0x201, 0x209, 0x211, 0x219];
|
|
const SAVE_CHAIRMAN_RECORD_START_SCAN_LIMIT: usize = 0x80;
|
|
|
|
fn parse_save_company_roster_probe(
|
|
bytes: &[u8],
|
|
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
|
selection_probe: Option<&SmpSaveWorldSelectionContextProbe>,
|
|
) -> Option<SmpLoadedCompanyRoster> {
|
|
let header_probe = header_probe?;
|
|
let observed_entry_count = header_probe.live_record_count as usize;
|
|
if observed_entry_count == 0 {
|
|
return Some(SmpLoadedCompanyRoster {
|
|
source_kind: "save-company-direct-records".to_string(),
|
|
semantic_family: "scenario-save-company-direct-records".to_string(),
|
|
observed_entry_count,
|
|
selected_company_id: selection_probe.map(|probe| probe.selected_company_id),
|
|
entries: Vec::new(),
|
|
});
|
|
}
|
|
|
|
let record_start_offset = detect_save_company_record_start_offset(bytes, header_probe)?;
|
|
let record_stride = header_probe.direct_record_stride as usize;
|
|
let base_offset = header_probe
|
|
.metadata_tag_offset
|
|
.checked_add(4)?
|
|
.checked_add(record_start_offset)?;
|
|
|
|
let mut entries = Vec::with_capacity(observed_entry_count);
|
|
for index in 0..observed_entry_count {
|
|
let record_offset = base_offset.checked_add(index.checked_mul(record_stride)?)?;
|
|
let company_id = read_u32_at(bytes, record_offset)?;
|
|
let active = read_u8_at(bytes, record_offset + SAVE_COMPANY_RECORD_ACTIVE_OFFSET)? != 0;
|
|
let linked_chairman_profile_id = parse_nonzero_u32(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_LINKED_CHAIRMAN_OFFSET,
|
|
)?;
|
|
let outstanding_shares = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_OUTSTANDING_SHARES_OFFSET,
|
|
)?;
|
|
let debt = parse_save_company_total_debt(bytes, record_offset)?;
|
|
let bond_count = read_u8_at(bytes, record_offset + SAVE_COMPANY_RECORD_BOND_COUNT_OFFSET)?;
|
|
let live_bond_slots = parse_save_company_live_bond_slots(bytes, record_offset)?;
|
|
let largest_live_bond_principal =
|
|
parse_save_company_largest_live_bond_principal(bytes, record_offset)?;
|
|
let highest_coupon_live_bond_principal =
|
|
parse_save_company_highest_coupon_live_bond_principal(bytes, record_offset)?;
|
|
let available_track_laying_capacity =
|
|
parse_save_company_available_track_laying_capacity(bytes, record_offset)?;
|
|
let mutable_support_scalar_raw_u32 = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_SUPPORT_SCALAR_OFFSET,
|
|
)?;
|
|
let young_company_support_scalar_raw_u32 = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_COMPANY_VALUE_OFFSET,
|
|
)?;
|
|
let support_progress_word = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_SUPPORT_PROGRESS_OFFSET,
|
|
)?;
|
|
let recent_per_share_cache_absolute_counter = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET,
|
|
)?;
|
|
let recent_per_share_cached_value_bits = read_u64_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET + 4,
|
|
)?;
|
|
let recent_per_share_subscore_raw_u32 = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_RECENT_PER_SHARE_SUBSCORE_OFFSET,
|
|
)?;
|
|
let cached_share_price_raw_u32 = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_CACHED_SHARE_PRICE_OFFSET,
|
|
)?;
|
|
let chairman_salary_baseline = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_BASELINE_OFFSET,
|
|
)?;
|
|
let chairman_salary_current = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_CURRENT_OFFSET,
|
|
)?;
|
|
let chairman_bonus_year = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_YEAR_OFFSET,
|
|
)?;
|
|
let chairman_bonus_amount = read_i32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_AMOUNT_OFFSET,
|
|
)?;
|
|
let founding_year = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_FOUNDING_YEAR_OFFSET,
|
|
)?;
|
|
let last_bankruptcy_year = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_LAST_BANKRUPTCY_YEAR_OFFSET,
|
|
)?;
|
|
let last_dividend_year = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_LAST_DIVIDEND_YEAR_OFFSET,
|
|
)?;
|
|
let current_issue_calendar_word = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET,
|
|
)?;
|
|
let current_issue_calendar_word_2 = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET_2,
|
|
)?;
|
|
let prior_issue_calendar_word = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET,
|
|
)?;
|
|
let prior_issue_calendar_word_2 = read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET_2,
|
|
)?;
|
|
let preferred_locomotive_engine_type_raw_u8 = read_u8_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_PREFERRED_LOCOMOTIVE_ENGINE_TYPE_OFFSET,
|
|
)?;
|
|
let city_connection_latch = read_u8_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_CITY_CONNECTION_LATCH_OFFSET,
|
|
)? != 0;
|
|
let linked_transit_latch = read_u8_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_LINKED_TRANSIT_LATCH_OFFSET,
|
|
)? != 0;
|
|
let linked_transit_route_anchor_entry_id = parse_nonzero_u32(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_LINKED_TRANSIT_ROUTE_ANCHOR_ENTRY_ID_OFFSET,
|
|
)?;
|
|
let linked_transit_route_anchor_fallback_counts =
|
|
SAVE_COMPANY_RECORD_LINKED_TRANSIT_ROUTE_ANCHOR_FALLBACK_COUNT_OFFSETS
|
|
.iter()
|
|
.map(|relative_offset| read_u32_at(bytes, record_offset + *relative_offset))
|
|
.collect::<Option<Vec<_>>>()?;
|
|
let merger_cooldown_year = parse_nonzero_u32(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_MERGER_COOLDOWN_OFFSET,
|
|
)?;
|
|
let takeover_cooldown_year = parse_nonzero_u32(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_TAKEOVER_COOLDOWN_OFFSET,
|
|
)?;
|
|
let stat_band_root_0cfb_candidates = build_save_company_stat_band_candidates(
|
|
bytes,
|
|
record_offset,
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET,
|
|
"stat_band_0cfb",
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS,
|
|
)?;
|
|
let stat_band_root_0d7f_candidates = build_save_company_stat_band_candidates(
|
|
bytes,
|
|
record_offset,
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET,
|
|
"stat_band_0d7f",
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS,
|
|
)?;
|
|
let stat_band_root_1c47_candidates = build_save_company_stat_band_candidates(
|
|
bytes,
|
|
record_offset,
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET,
|
|
"stat_band_1c47",
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS,
|
|
)?;
|
|
let year_stat_family_qword_bits = build_save_company_stat_qword_bits(
|
|
bytes,
|
|
record_offset,
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET,
|
|
SAVE_COMPANY_RECORD_YEAR_STAT_FAMILY_QWORD_COUNT,
|
|
)?;
|
|
let special_stat_family_232a_qword_bits = build_save_company_stat_qword_bits(
|
|
bytes,
|
|
record_offset,
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET,
|
|
SAVE_COMPANY_RECORD_SPECIAL_STAT_FAMILY_232A_QWORD_COUNT,
|
|
)?;
|
|
let direct_control_transfer_float_fields_raw_u32 = build_save_u32_field_map(
|
|
bytes,
|
|
record_offset,
|
|
&SAVE_COMPANY_RECORD_DIRECT_CONTROL_TRANSFER_FLOAT_FIELDS,
|
|
)?;
|
|
let direct_control_transfer_int_fields_raw_u32 = build_save_u32_field_map(
|
|
bytes,
|
|
record_offset,
|
|
&SAVE_COMPANY_RECORD_DIRECT_CONTROL_TRANSFER_INT_FIELDS,
|
|
)?;
|
|
let issue_opinion_terms_raw_i32 = build_save_i32_term_strip(
|
|
bytes,
|
|
record_offset,
|
|
SAVE_COMPANY_RECORD_ISSUE_OPINION_TERMS_OFFSET,
|
|
SAVE_COMPANY_RECORD_ISSUE_OPINION_TERM_COUNT,
|
|
)?;
|
|
let current_cash = decode_save_company_current_year_stat_slot(
|
|
&year_stat_family_qword_bits,
|
|
crate::RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH,
|
|
)
|
|
.and_then(round_f64_to_i64)
|
|
.unwrap_or(0);
|
|
entries.push(SmpLoadedCompanyRosterEntry {
|
|
company_id,
|
|
active,
|
|
controller_kind: RuntimeCompanyControllerKind::Unknown,
|
|
current_cash,
|
|
debt,
|
|
credit_rating_score: None,
|
|
prime_rate: None,
|
|
available_track_laying_capacity,
|
|
track_piece_counts: RuntimeTrackPieceCounts::default(),
|
|
linked_chairman_profile_id,
|
|
book_value_per_share: 0,
|
|
investor_confidence: 0,
|
|
management_attitude: 0,
|
|
takeover_cooldown_year,
|
|
merger_cooldown_year,
|
|
preferred_locomotive_engine_type_raw_u8: Some(preferred_locomotive_engine_type_raw_u8),
|
|
market_state: Some(RuntimeCompanyMarketState {
|
|
outstanding_shares,
|
|
bond_count,
|
|
live_bond_slots,
|
|
largest_live_bond_principal,
|
|
highest_coupon_live_bond_principal,
|
|
mutable_support_scalar_raw_u32,
|
|
young_company_support_scalar_raw_u32,
|
|
support_progress_word,
|
|
recent_per_share_cache_absolute_counter,
|
|
recent_per_share_cached_value_bits,
|
|
recent_per_share_subscore_raw_u32,
|
|
cached_share_price_raw_u32,
|
|
chairman_salary_baseline,
|
|
chairman_salary_current,
|
|
chairman_bonus_year,
|
|
chairman_bonus_amount,
|
|
founding_year,
|
|
last_bankruptcy_year,
|
|
last_dividend_year,
|
|
current_issue_calendar_word,
|
|
current_issue_calendar_word_2,
|
|
prior_issue_calendar_word,
|
|
prior_issue_calendar_word_2,
|
|
city_connection_latch,
|
|
linked_transit_latch,
|
|
linked_transit_route_anchor_entry_id,
|
|
linked_transit_route_anchor_fallback_counts,
|
|
stat_band_root_0cfb_candidates: stat_band_root_0cfb_candidates
|
|
.iter()
|
|
.map(runtime_company_stat_band_candidate_from_save)
|
|
.collect(),
|
|
stat_band_root_0d7f_candidates: stat_band_root_0d7f_candidates
|
|
.iter()
|
|
.map(runtime_company_stat_band_candidate_from_save)
|
|
.collect(),
|
|
stat_band_root_1c47_candidates: stat_band_root_1c47_candidates
|
|
.iter()
|
|
.map(runtime_company_stat_band_candidate_from_save)
|
|
.collect(),
|
|
year_stat_family_qword_bits,
|
|
special_stat_family_232a_qword_bits,
|
|
issue_opinion_terms_raw_i32,
|
|
direct_control_transfer_float_fields_raw_u32,
|
|
direct_control_transfer_int_fields_raw_u32,
|
|
}),
|
|
});
|
|
}
|
|
|
|
Some(SmpLoadedCompanyRoster {
|
|
source_kind: "save-company-direct-records".to_string(),
|
|
semantic_family: "scenario-save-company-direct-records".to_string(),
|
|
observed_entry_count,
|
|
selected_company_id: selection_probe.map(|probe| probe.selected_company_id),
|
|
entries,
|
|
})
|
|
}
|
|
|
|
fn runtime_company_stat_band_candidate_from_save(
|
|
candidate: &SmpSaveDwordCandidate,
|
|
) -> crate::RuntimeCompanyStatBandCandidate {
|
|
crate::RuntimeCompanyStatBandCandidate {
|
|
label: candidate.label.clone(),
|
|
relative_offset: candidate.relative_offset,
|
|
relative_offset_hex: candidate.relative_offset_hex.clone(),
|
|
raw_u32: candidate.raw_u32,
|
|
raw_u32_hex: candidate.raw_u32_hex.clone(),
|
|
value_i32: candidate.value_i32,
|
|
value_f32_text: format!("{:.6}", candidate.value_f32),
|
|
}
|
|
}
|
|
|
|
fn build_save_company_stat_band_candidates(
|
|
bytes: &[u8],
|
|
record_offset: usize,
|
|
root_offset: usize,
|
|
label_prefix: &str,
|
|
word_count: usize,
|
|
) -> Option<Vec<SmpSaveDwordCandidate>> {
|
|
(0..word_count)
|
|
.map(|index| {
|
|
let relative_offset = root_offset.checked_add(index.checked_mul(4)?)?;
|
|
let label = format!("{label_prefix}_word_{}", index + 1);
|
|
build_save_dword_candidate(bytes, record_offset, &label, relative_offset)
|
|
})
|
|
.collect::<Option<Vec<_>>>()
|
|
}
|
|
|
|
fn build_save_company_stat_qword_bits(
|
|
bytes: &[u8],
|
|
record_offset: usize,
|
|
root_offset: usize,
|
|
qword_count: usize,
|
|
) -> Option<Vec<u64>> {
|
|
(0..qword_count)
|
|
.map(|index| {
|
|
let relative_offset = root_offset.checked_add(index.checked_mul(8)?)?;
|
|
read_u64_at(bytes, record_offset + relative_offset)
|
|
})
|
|
.collect::<Option<Vec<_>>>()
|
|
}
|
|
|
|
fn build_save_i32_term_strip(
|
|
bytes: &[u8],
|
|
record_offset: usize,
|
|
root_offset: usize,
|
|
value_count: usize,
|
|
) -> Option<Vec<i32>> {
|
|
(0..value_count)
|
|
.map(|index| {
|
|
let relative_offset = root_offset.checked_add(index.checked_mul(4)?)?;
|
|
read_i32_at(bytes, record_offset + relative_offset)
|
|
})
|
|
.collect::<Option<Vec<_>>>()
|
|
}
|
|
|
|
fn build_save_u32_field_map(
|
|
bytes: &[u8],
|
|
record_offset: usize,
|
|
offsets: &[usize],
|
|
) -> Option<BTreeMap<u32, u32>> {
|
|
let mut fields = BTreeMap::new();
|
|
for relative_offset in offsets {
|
|
fields.insert(
|
|
u32::try_from(*relative_offset).ok()?,
|
|
read_u32_at(bytes, record_offset + *relative_offset)?,
|
|
);
|
|
}
|
|
Some(fields)
|
|
}
|
|
|
|
fn decode_save_company_current_year_stat_slot(
|
|
year_stat_family_qword_bits: &[u64],
|
|
slot_id: u32,
|
|
) -> Option<f64> {
|
|
let index = slot_id.checked_mul(crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN)? as usize;
|
|
let value = f64::from_bits(*year_stat_family_qword_bits.get(index)?);
|
|
value.is_finite().then_some(value)
|
|
}
|
|
|
|
fn detect_save_company_record_start_offset(
|
|
bytes: &[u8],
|
|
header_probe: &SmpSaveTaggedCollectionHeaderProbe,
|
|
) -> Option<usize> {
|
|
let observed_entry_count = header_probe.live_record_count as usize;
|
|
let record_stride = header_probe.direct_record_stride as usize;
|
|
let scan_limit = SAVE_COMPANY_RECORD_START_SCAN_LIMIT.min(record_stride);
|
|
let base_offset = header_probe.metadata_tag_offset.checked_add(4)?;
|
|
let mut best_start = None;
|
|
let mut best_score = 0usize;
|
|
|
|
for start in 0..scan_limit {
|
|
let mut score = 0usize;
|
|
let mut seen_ids = std::collections::BTreeSet::new();
|
|
let mut valid = true;
|
|
for index in 0..observed_entry_count {
|
|
let record_offset = match base_offset
|
|
.checked_add(start)
|
|
.and_then(|offset| offset.checked_add(index.checked_mul(record_stride)?))
|
|
{
|
|
Some(offset) => offset,
|
|
None => {
|
|
valid = false;
|
|
break;
|
|
}
|
|
};
|
|
let company_id = match read_u32_at(bytes, record_offset) {
|
|
Some(value)
|
|
if value >= 1
|
|
&& value <= header_probe.live_id_bound
|
|
&& seen_ids.insert(value) =>
|
|
{
|
|
value
|
|
}
|
|
_ => {
|
|
valid = false;
|
|
break;
|
|
}
|
|
};
|
|
let active = match read_u8_at(bytes, record_offset + SAVE_COMPANY_RECORD_ACTIVE_OFFSET)
|
|
{
|
|
Some(0 | 1) => {
|
|
score += 1;
|
|
true
|
|
}
|
|
_ => {
|
|
valid = false;
|
|
break;
|
|
}
|
|
};
|
|
let linked = match read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_LINKED_CHAIRMAN_OFFSET,
|
|
) {
|
|
Some(value) if value <= 0x100 => value,
|
|
_ => {
|
|
valid = false;
|
|
break;
|
|
}
|
|
};
|
|
let name = match read_ascii_c_string_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_NAME_OFFSET,
|
|
SAVE_COMPANY_RECORD_NAME_MAX_LEN,
|
|
) {
|
|
Some(name) if !name.is_empty() && name.chars().all(is_save_name_char) => name,
|
|
_ => {
|
|
valid = false;
|
|
break;
|
|
}
|
|
};
|
|
score += name.len();
|
|
if active {
|
|
score += 8;
|
|
}
|
|
if linked != 0 {
|
|
score += 2;
|
|
}
|
|
if company_id == (index + 1) as u32 {
|
|
score += 4;
|
|
}
|
|
}
|
|
if valid && score > best_score {
|
|
best_score = score;
|
|
best_start = Some(start);
|
|
}
|
|
}
|
|
|
|
best_start
|
|
}
|
|
|
|
fn parse_save_company_total_debt(bytes: &[u8], record_offset: usize) -> Option<u64> {
|
|
let bond_count =
|
|
read_u8_at(bytes, record_offset + SAVE_COMPANY_RECORD_BOND_COUNT_OFFSET)? as usize;
|
|
let mut total = 0u64;
|
|
for slot_index in 0..bond_count {
|
|
let slot_offset = record_offset
|
|
.checked_add(SAVE_COMPANY_RECORD_BOND_TABLE_OFFSET)?
|
|
.checked_add(slot_index.checked_mul(SAVE_COMPANY_RECORD_BOND_SLOT_STRIDE)?)?;
|
|
let principal = read_i32_at(bytes, slot_offset)?;
|
|
if principal > 0 {
|
|
total = total.checked_add(principal as u64)?;
|
|
}
|
|
}
|
|
Some(total)
|
|
}
|
|
|
|
fn parse_save_company_live_bond_slots(
|
|
bytes: &[u8],
|
|
record_offset: usize,
|
|
) -> Option<Vec<crate::RuntimeCompanyBondSlot>> {
|
|
let bond_count =
|
|
read_u8_at(bytes, record_offset + SAVE_COMPANY_RECORD_BOND_COUNT_OFFSET)? as usize;
|
|
let mut slots = Vec::new();
|
|
for slot_index in 0..bond_count {
|
|
let slot_offset = record_offset
|
|
.checked_add(SAVE_COMPANY_RECORD_BOND_TABLE_OFFSET)?
|
|
.checked_add(slot_index.checked_mul(SAVE_COMPANY_RECORD_BOND_SLOT_STRIDE)?)?;
|
|
let principal = read_i32_at(bytes, slot_offset)?;
|
|
if principal <= 0 {
|
|
continue;
|
|
}
|
|
let maturity_year = read_u32_at(bytes, slot_offset + 4)?;
|
|
let coupon_rate_raw_u32 = read_u32_at(bytes, slot_offset + 8)?;
|
|
let coupon_rate = f32::from_bits(coupon_rate_raw_u32);
|
|
if !coupon_rate.is_finite() {
|
|
continue;
|
|
}
|
|
slots.push(crate::RuntimeCompanyBondSlot {
|
|
slot_index: slot_index as u32,
|
|
principal: principal as u32,
|
|
maturity_year,
|
|
coupon_rate_raw_u32,
|
|
});
|
|
}
|
|
Some(slots)
|
|
}
|
|
|
|
fn parse_save_company_largest_live_bond_principal(
|
|
bytes: &[u8],
|
|
record_offset: usize,
|
|
) -> Option<Option<u32>> {
|
|
let mut largest_live_principal: Option<u32> = None;
|
|
for slot in parse_save_company_live_bond_slots(bytes, record_offset)? {
|
|
largest_live_principal = Some(match largest_live_principal {
|
|
Some(current) => current.max(slot.principal),
|
|
None => slot.principal,
|
|
});
|
|
}
|
|
Some(largest_live_principal)
|
|
}
|
|
|
|
fn parse_save_company_highest_coupon_live_bond_principal(
|
|
bytes: &[u8],
|
|
record_offset: usize,
|
|
) -> Option<Option<u32>> {
|
|
let mut highest_coupon_principal = None;
|
|
let mut highest_coupon_rate = None;
|
|
for slot in parse_save_company_live_bond_slots(bytes, record_offset)? {
|
|
let coupon_rate = f32::from_bits(slot.coupon_rate_raw_u32);
|
|
match highest_coupon_rate {
|
|
Some(current_rate) if coupon_rate < current_rate => {}
|
|
Some(current_rate) if coupon_rate == current_rate => {
|
|
if let Some(current_principal) = highest_coupon_principal {
|
|
if slot.principal > current_principal {
|
|
highest_coupon_principal = Some(slot.principal);
|
|
}
|
|
}
|
|
}
|
|
_ => {
|
|
highest_coupon_rate = Some(coupon_rate);
|
|
highest_coupon_principal = Some(slot.principal);
|
|
}
|
|
}
|
|
}
|
|
Some(highest_coupon_principal)
|
|
}
|
|
|
|
fn parse_save_company_available_track_laying_capacity(
|
|
bytes: &[u8],
|
|
record_offset: usize,
|
|
) -> Option<Option<u32>> {
|
|
let raw = read_i32_at(
|
|
bytes,
|
|
record_offset + SAVE_COMPANY_RECORD_TRACK_LAYING_CAPACITY_OFFSET,
|
|
)?;
|
|
if raw < 0 {
|
|
Some(None)
|
|
} else {
|
|
Some(Some(raw as u32))
|
|
}
|
|
}
|
|
|
|
fn build_save_dword_candidate(
|
|
bytes: &[u8],
|
|
record_offset: usize,
|
|
label: &str,
|
|
relative_offset: usize,
|
|
) -> Option<SmpSaveDwordCandidate> {
|
|
let raw_u32 = read_u32_at(bytes, record_offset + relative_offset)?;
|
|
Some(SmpSaveDwordCandidate {
|
|
label: label.to_string(),
|
|
relative_offset,
|
|
relative_offset_hex: format!("0x{relative_offset:x}"),
|
|
raw_u32,
|
|
raw_u32_hex: format!("0x{raw_u32:08x}"),
|
|
value_i32: raw_u32 as i32,
|
|
value_f32: f32::from_bits(raw_u32),
|
|
})
|
|
}
|
|
|
|
fn build_save_qword_candidate(
|
|
bytes: &[u8],
|
|
record_offset: usize,
|
|
relative_offset: usize,
|
|
) -> Option<SmpSaveScalarCandidate> {
|
|
let raw_u64 = read_u64_at(bytes, record_offset + relative_offset)?;
|
|
Some(SmpSaveScalarCandidate {
|
|
relative_offset,
|
|
relative_offset_hex: format!("0x{relative_offset:x}"),
|
|
raw_u64,
|
|
raw_u64_hex: format!("0x{raw_u64:016x}"),
|
|
value_i64: raw_u64 as i64,
|
|
value_f64: f64::from_bits(raw_u64),
|
|
})
|
|
}
|
|
|
|
fn derive_chairman_holdings_share_price_total(
|
|
holdings_by_company: &BTreeMap<u32, u32>,
|
|
company_share_prices: &BTreeMap<u32, i64>,
|
|
) -> Option<i64> {
|
|
let mut total = 0i64;
|
|
for (company_id, units) in holdings_by_company {
|
|
let share_price = *company_share_prices.get(company_id)?;
|
|
total = total.checked_add((*units as i64).checked_mul(share_price)?)?;
|
|
}
|
|
Some(total)
|
|
}
|
|
|
|
fn derive_chairman_cached_purchasing_power_total(
|
|
current_cash: i64,
|
|
cached_scalar_candidates: &[SmpSaveScalarCandidate],
|
|
) -> Option<i64> {
|
|
let strongest_cached_total = cached_scalar_candidates
|
|
.iter()
|
|
.filter_map(|candidate| round_f64_to_i64(candidate.value_f64))
|
|
.filter(|value| *value >= 0)
|
|
.max()?;
|
|
current_cash.checked_add(strongest_cached_total)
|
|
}
|
|
|
|
fn build_save_world_selection_role_analysis(
|
|
probe: &SmpSaveWorldSelectionContextProbe,
|
|
) -> SmpSaveWorldSelectionRoleAnalysis {
|
|
let chairman_slots = probe
|
|
.chairman_slot_selectors
|
|
.iter()
|
|
.copied()
|
|
.zip(probe.chairman_role_gate_bytes.iter().copied())
|
|
.enumerate()
|
|
.map(|(slot_index, (selector_byte, role_gate_byte))| {
|
|
SmpSaveWorldSelectionRoleAnalysisEntry {
|
|
slot_index,
|
|
selector_byte,
|
|
selector_byte_hex: format!("0x{selector_byte:02x}"),
|
|
role_gate_byte,
|
|
role_gate_byte_hex: format!("0x{role_gate_byte:02x}"),
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
SmpSaveWorldSelectionRoleAnalysis {
|
|
selected_company_id: probe.selected_company_id,
|
|
selected_chairman_profile_id: probe.selected_chairman_profile_id,
|
|
campaign_override_flag: probe.campaign_override_flag,
|
|
campaign_override_flag_hex: probe.campaign_override_flag_hex.clone(),
|
|
chairman_slots,
|
|
}
|
|
}
|
|
|
|
fn parse_save_chairman_profile_table_probe(
|
|
bytes: &[u8],
|
|
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
|
selection_probe: Option<&SmpSaveWorldSelectionContextProbe>,
|
|
company_header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
|
) -> Option<SmpLoadedChairmanProfileTable> {
|
|
let header_probe = header_probe?;
|
|
let observed_entry_count = header_probe.live_record_count as usize;
|
|
if observed_entry_count == 0 {
|
|
return Some(SmpLoadedChairmanProfileTable {
|
|
source_kind: "save-chairman-profile-direct-records".to_string(),
|
|
semantic_family: "scenario-save-chairman-profile-direct-records".to_string(),
|
|
observed_entry_count,
|
|
selected_chairman_profile_id: selection_probe
|
|
.map(|probe| probe.selected_chairman_profile_id),
|
|
entries: Vec::new(),
|
|
});
|
|
}
|
|
|
|
let record_start_offset =
|
|
detect_save_chairman_profile_record_start_offset(bytes, header_probe)?;
|
|
let record_stride = header_probe.direct_record_stride as usize;
|
|
let base_offset = header_probe
|
|
.metadata_tag_offset
|
|
.checked_add(4)?
|
|
.checked_add(record_start_offset)?;
|
|
let company_id_bound = company_header_probe
|
|
.map(|probe| probe.live_id_bound)
|
|
.unwrap_or(0);
|
|
|
|
let mut entries = Vec::with_capacity(observed_entry_count);
|
|
for index in 0..observed_entry_count {
|
|
let record_offset = base_offset.checked_add(index.checked_mul(record_stride)?)?;
|
|
let profile_id = read_u32_at(bytes, record_offset)?;
|
|
let active = read_u32_at(bytes, record_offset + 4)? != 0;
|
|
let name = read_ascii_c_string_at(
|
|
bytes,
|
|
record_offset + SAVE_CHAIRMAN_RECORD_NAME_OFFSET,
|
|
SAVE_CHAIRMAN_RECORD_NAME_MAX_LEN,
|
|
)?;
|
|
let current_cash = round_f64_to_i64(read_f64_at(
|
|
bytes,
|
|
record_offset + SAVE_CHAIRMAN_RECORD_CASH_OFFSET,
|
|
)?)?;
|
|
let linked_company_id = parse_nonzero_u32(
|
|
bytes,
|
|
record_offset + SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET,
|
|
)?;
|
|
let personality_byte_0x291 = read_u8_at(
|
|
bytes,
|
|
record_offset + SAVE_CHAIRMAN_RECORD_PERSONALITY_BYTE_0X291_OFFSET,
|
|
)?;
|
|
let cache_0 = round_f64_to_i64(read_f64_at(
|
|
bytes,
|
|
record_offset + SAVE_CHAIRMAN_RECORD_CACHE_0_OFFSET,
|
|
)?)?;
|
|
let cache_1 = round_f64_to_i64(read_f64_at(
|
|
bytes,
|
|
record_offset + SAVE_CHAIRMAN_RECORD_CACHE_1_OFFSET,
|
|
)?)?;
|
|
let cached_scalar_candidates = SAVE_CHAIRMAN_RECORD_CACHE_CANDIDATE_OFFSETS
|
|
.iter()
|
|
.map(|relative_offset| {
|
|
build_save_qword_candidate(bytes, record_offset, *relative_offset)
|
|
})
|
|
.collect::<Option<Vec<_>>>()?;
|
|
let issue_opinion_terms_raw_i32 = build_save_i32_term_strip(
|
|
bytes,
|
|
record_offset,
|
|
SAVE_CHAIRMAN_RECORD_ISSUE_OPINION_TERMS_OFFSET,
|
|
SAVE_CHAIRMAN_RECORD_ISSUE_OPINION_TERM_COUNT,
|
|
)?;
|
|
let holdings_value_total = cache_0.max(cache_1).max(0);
|
|
let net_worth_total = current_cash.saturating_add(holdings_value_total);
|
|
let purchasing_power_total =
|
|
derive_chairman_cached_purchasing_power_total(current_cash, &cached_scalar_candidates)
|
|
.unwrap_or(net_worth_total);
|
|
let mut company_holdings = BTreeMap::new();
|
|
for company_id in 1..=company_id_bound {
|
|
let slot_offset = record_offset
|
|
.checked_add(SAVE_CHAIRMAN_RECORD_HOLDINGS_BASE_OFFSET)?
|
|
.checked_add((company_id as usize).checked_mul(4)?)?;
|
|
let units = read_u32_at(bytes, slot_offset)?;
|
|
if units != 0 {
|
|
company_holdings.insert(company_id, units);
|
|
}
|
|
}
|
|
entries.push(SmpLoadedChairmanProfileEntry {
|
|
profile_id,
|
|
name,
|
|
active,
|
|
current_cash,
|
|
linked_company_id,
|
|
company_holdings,
|
|
holdings_value_total,
|
|
net_worth_total,
|
|
purchasing_power_total,
|
|
personality_byte_0x291: Some(personality_byte_0x291),
|
|
issue_opinion_terms_raw_i32,
|
|
});
|
|
}
|
|
|
|
Some(SmpLoadedChairmanProfileTable {
|
|
source_kind: "save-chairman-profile-direct-records".to_string(),
|
|
semantic_family: "scenario-save-chairman-profile-direct-records".to_string(),
|
|
observed_entry_count,
|
|
selected_chairman_profile_id: selection_probe
|
|
.map(|probe| probe.selected_chairman_profile_id),
|
|
entries,
|
|
})
|
|
}
|
|
|
|
fn detect_save_chairman_profile_record_start_offset(
|
|
bytes: &[u8],
|
|
header_probe: &SmpSaveTaggedCollectionHeaderProbe,
|
|
) -> Option<usize> {
|
|
let observed_entry_count = header_probe.live_record_count as usize;
|
|
let record_stride = header_probe.direct_record_stride as usize;
|
|
let scan_limit = SAVE_CHAIRMAN_RECORD_START_SCAN_LIMIT.min(record_stride);
|
|
let base_offset = header_probe.metadata_tag_offset.checked_add(4)?;
|
|
let mut best_start = None;
|
|
let mut best_score = 0usize;
|
|
|
|
for start in 0..scan_limit {
|
|
let mut score = 0usize;
|
|
let mut seen_ids = std::collections::BTreeSet::new();
|
|
let mut valid = true;
|
|
for index in 0..observed_entry_count {
|
|
let record_offset = match base_offset
|
|
.checked_add(start)
|
|
.and_then(|offset| offset.checked_add(index.checked_mul(record_stride)?))
|
|
{
|
|
Some(offset) => offset,
|
|
None => {
|
|
valid = false;
|
|
break;
|
|
}
|
|
};
|
|
let profile_id = match read_u32_at(bytes, record_offset) {
|
|
Some(value)
|
|
if value >= 1
|
|
&& value <= header_probe.live_id_bound
|
|
&& seen_ids.insert(value) =>
|
|
{
|
|
value
|
|
}
|
|
_ => {
|
|
valid = false;
|
|
break;
|
|
}
|
|
};
|
|
match read_u32_at(bytes, record_offset + 4) {
|
|
Some(0 | 1) => score += 1,
|
|
_ => {
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
let name = match read_ascii_c_string_at(
|
|
bytes,
|
|
record_offset + SAVE_CHAIRMAN_RECORD_NAME_OFFSET,
|
|
SAVE_CHAIRMAN_RECORD_NAME_MAX_LEN,
|
|
) {
|
|
Some(name) if !name.is_empty() && name.chars().all(is_save_name_char) => name,
|
|
_ => {
|
|
valid = false;
|
|
break;
|
|
}
|
|
};
|
|
match read_u32_at(
|
|
bytes,
|
|
record_offset + SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET,
|
|
) {
|
|
Some(value) if value <= 0x100 => score += (value != 0) as usize,
|
|
_ => {
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
match read_f64_at(bytes, record_offset + SAVE_CHAIRMAN_RECORD_CASH_OFFSET) {
|
|
Some(value) if value.is_finite() && value.abs() < 1.0e12 => {
|
|
score += name.len() + 4;
|
|
}
|
|
_ => {
|
|
valid = false;
|
|
break;
|
|
}
|
|
}
|
|
if profile_id == (index + 1) as u32 {
|
|
score += 4;
|
|
}
|
|
}
|
|
if valid && score > best_score {
|
|
best_score = score;
|
|
best_start = Some(start);
|
|
}
|
|
}
|
|
|
|
best_start
|
|
}
|
|
|
|
fn is_save_name_char(ch: char) -> bool {
|
|
ch.is_ascii_alphanumeric()
|
|
|| matches!(
|
|
ch,
|
|
' ' | '&' | '\'' | ',' | '.' | '-' | '/' | '(' | ')' | ':'
|
|
)
|
|
}
|
|
|
|
fn known_cargo_slot_definition(slot_id: u32) -> Option<KnownCargoSlotDefinition> {
|
|
KNOWN_CARGO_SLOT_DEFINITIONS
|
|
.iter()
|
|
.copied()
|
|
.find(|definition| definition.slot_id == slot_id)
|
|
}
|
|
|
|
fn known_cargo_slot_definition_for_descriptor_id(
|
|
descriptor_id: u32,
|
|
) -> Option<KnownCargoSlotDefinition> {
|
|
KNOWN_CARGO_SLOT_DEFINITIONS
|
|
.iter()
|
|
.copied()
|
|
.find(|definition| definition.descriptor_id == descriptor_id)
|
|
}
|
|
|
|
fn runtime_cargo_class_name(cargo_class: RuntimeCargoClass) -> &'static str {
|
|
match cargo_class {
|
|
RuntimeCargoClass::Factory => "factory",
|
|
RuntimeCargoClass::FarmMine => "farm_mine",
|
|
RuntimeCargoClass::Other => "other",
|
|
}
|
|
}
|
|
|
|
fn parse_event_runtime_collection_summary(
|
|
bytes: &[u8],
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
save_load_summary: Option<&SmpSaveLoadSummary>,
|
|
) -> Option<SmpLoadedEventRuntimeCollectionSummary> {
|
|
parse_event_runtime_collection_summary_with_tag_width(
|
|
bytes,
|
|
container_profile,
|
|
save_load_summary,
|
|
2,
|
|
)
|
|
.or_else(|| {
|
|
parse_event_runtime_collection_summary_with_tag_width(
|
|
bytes,
|
|
container_profile,
|
|
save_load_summary,
|
|
4,
|
|
)
|
|
})
|
|
}
|
|
|
|
fn parse_event_runtime_collection_summary_with_tag_width(
|
|
bytes: &[u8],
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
save_load_summary: Option<&SmpSaveLoadSummary>,
|
|
tag_width: usize,
|
|
) -> Option<SmpLoadedEventRuntimeCollectionSummary> {
|
|
let (metadata_offsets, record_offsets, close_offsets) = match tag_width {
|
|
2 => (
|
|
find_u16_le_offsets(bytes, EVENT_RUNTIME_COLLECTION_METADATA_TAG),
|
|
find_u16_le_offsets(bytes, EVENT_RUNTIME_COLLECTION_RECORDS_TAG),
|
|
find_u16_le_offsets(bytes, EVENT_RUNTIME_COLLECTION_CLOSE_TAG),
|
|
),
|
|
4 => (
|
|
find_u32_le_offsets(bytes, EVENT_RUNTIME_COLLECTION_METADATA_TAG as u32),
|
|
find_u32_le_offsets(bytes, EVENT_RUNTIME_COLLECTION_RECORDS_TAG as u32),
|
|
find_u32_le_offsets(bytes, EVENT_RUNTIME_COLLECTION_CLOSE_TAG as u32),
|
|
),
|
|
_ => return None,
|
|
};
|
|
|
|
for metadata_tag_offset in metadata_offsets {
|
|
let packed_state_version = read_u32_at(bytes, metadata_tag_offset + tag_width)?;
|
|
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 + tag_width + 4)?;
|
|
let close_tag_offset = close_offsets
|
|
.iter()
|
|
.copied()
|
|
.find(|offset| *offset > records_tag_offset)?;
|
|
let metadata_payload =
|
|
bytes.get(metadata_tag_offset + tag_width + 4..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()?;
|
|
|
|
let records_payload = bytes.get(records_tag_offset + tag_width..close_tag_offset)?;
|
|
let (source_kind, live_entry_ids) = if direct_collection_flag == 0 && tag_width == 4 {
|
|
(
|
|
"packed-event-runtime-collection-nondirect".to_string(),
|
|
(1..=u32::try_from(live_record_count).ok()?).collect::<Vec<_>>(),
|
|
)
|
|
} else {
|
|
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;
|
|
}
|
|
(
|
|
"packed-event-runtime-collection".to_string(),
|
|
live_entry_ids,
|
|
)
|
|
};
|
|
let records = if source_kind == "packed-event-runtime-collection-nondirect" {
|
|
try_parse_nondirect_event_runtime_record_summaries(
|
|
records_payload,
|
|
records_tag_offset + tag_width,
|
|
&live_entry_ids,
|
|
)
|
|
.unwrap_or_else(|| {
|
|
parse_event_runtime_record_summaries(
|
|
records_payload,
|
|
records_tag_offset + tag_width,
|
|
&live_entry_ids,
|
|
)
|
|
})
|
|
} else {
|
|
parse_event_runtime_record_summaries(
|
|
records_payload,
|
|
records_tag_offset + tag_width,
|
|
&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();
|
|
let records_with_trigger_kind = records
|
|
.iter()
|
|
.filter(|record| record.trigger_kind.is_some())
|
|
.count();
|
|
let records_missing_trigger_kind = records.len().saturating_sub(records_with_trigger_kind);
|
|
let nondirect_compact_record_count = records
|
|
.iter()
|
|
.filter(|record| record.payload_family == "real_packed_nondirect_compact_v1")
|
|
.count();
|
|
let nondirect_compact_records_missing_trigger_kind = records
|
|
.iter()
|
|
.filter(|record| {
|
|
record.payload_family == "real_packed_nondirect_compact_v1"
|
|
&& record.trigger_kind.is_none()
|
|
})
|
|
.count();
|
|
let mut trigger_kinds_present = records
|
|
.iter()
|
|
.filter_map(|record| record.trigger_kind)
|
|
.collect::<Vec<_>>();
|
|
trigger_kinds_present.sort_unstable();
|
|
trigger_kinds_present.dedup();
|
|
let mut mutation_candidate_record_indexes = records
|
|
.iter()
|
|
.filter(|record| {
|
|
record.grouped_effect_rows.iter().any(|row| {
|
|
opcode_reaches_world_apply_compact_runtime_effect_dispatch_strip(row.opcode)
|
|
})
|
|
})
|
|
.map(|record| record.record_index)
|
|
.collect::<Vec<_>>();
|
|
mutation_candidate_record_indexes.sort_unstable();
|
|
mutation_candidate_record_indexes.dedup();
|
|
let mut mutation_candidate_opcodes = records
|
|
.iter()
|
|
.flat_map(|record| record.grouped_effect_rows.iter().map(|row| row.opcode))
|
|
.filter(|opcode| {
|
|
opcode_reaches_world_apply_compact_runtime_effect_dispatch_strip(*opcode)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
mutation_candidate_opcodes.sort_unstable();
|
|
mutation_candidate_opcodes.dedup();
|
|
let mut opcode_0x08_record_indexes = records
|
|
.iter()
|
|
.filter(|record| {
|
|
record
|
|
.grouped_effect_rows
|
|
.iter()
|
|
.any(|row| row.opcode == 0x08)
|
|
})
|
|
.map(|record| record.record_index)
|
|
.collect::<Vec<_>>();
|
|
opcode_0x08_record_indexes.sort_unstable();
|
|
opcode_0x08_record_indexes.dedup();
|
|
let mut add_building_dispatch_strip_record_indexes = records
|
|
.iter()
|
|
.filter(|record| {
|
|
record.grouped_effect_rows.iter().any(|row| {
|
|
opcode_reaches_world_apply_compact_runtime_effect_dispatch_strip(row.opcode)
|
|
&& compact_event_dispatch_add_building_descriptor_id(row.descriptor_id)
|
|
})
|
|
})
|
|
.map(|record| record.record_index)
|
|
.collect::<Vec<_>>();
|
|
add_building_dispatch_strip_record_indexes.sort_unstable();
|
|
add_building_dispatch_strip_record_indexes.dedup();
|
|
let add_building_dispatch_strip_records_with_trigger_kind = records
|
|
.iter()
|
|
.filter(|record| {
|
|
record.trigger_kind.is_some()
|
|
&& record.grouped_effect_rows.iter().any(|row| {
|
|
opcode_reaches_world_apply_compact_runtime_effect_dispatch_strip(row.opcode)
|
|
&& compact_event_dispatch_add_building_descriptor_id(row.descriptor_id)
|
|
})
|
|
})
|
|
.count();
|
|
let add_building_dispatch_strip_records_missing_trigger_kind = records
|
|
.iter()
|
|
.filter(|record| {
|
|
record.trigger_kind.is_none()
|
|
&& record.grouped_effect_rows.iter().any(|row| {
|
|
opcode_reaches_world_apply_compact_runtime_effect_dispatch_strip(row.opcode)
|
|
&& compact_event_dispatch_add_building_descriptor_id(row.descriptor_id)
|
|
})
|
|
})
|
|
.count();
|
|
let mut mutation_candidate_descriptor_labels = records
|
|
.iter()
|
|
.flat_map(|record| record.grouped_effect_rows.iter())
|
|
.filter(|row| {
|
|
opcode_reaches_world_apply_compact_runtime_effect_dispatch_strip(row.opcode)
|
|
})
|
|
.filter_map(|row| row.descriptor_label.clone())
|
|
.collect::<Vec<_>>();
|
|
mutation_candidate_descriptor_labels.sort_unstable();
|
|
mutation_candidate_descriptor_labels.dedup();
|
|
let mut add_building_dispatch_strip_descriptor_labels = records
|
|
.iter()
|
|
.flat_map(|record| record.grouped_effect_rows.iter())
|
|
.filter(|row| {
|
|
opcode_reaches_world_apply_compact_runtime_effect_dispatch_strip(row.opcode)
|
|
&& compact_event_dispatch_add_building_descriptor_id(row.descriptor_id)
|
|
})
|
|
.filter_map(|row| row.descriptor_label.clone())
|
|
.collect::<Vec<_>>();
|
|
add_building_dispatch_strip_descriptor_labels.sort_unstable();
|
|
add_building_dispatch_strip_descriptor_labels.dedup();
|
|
let mut add_building_dispatch_strip_row_shape_families = records
|
|
.iter()
|
|
.filter(|record| {
|
|
record.grouped_effect_rows.iter().any(|row| {
|
|
opcode_reaches_world_apply_compact_runtime_effect_dispatch_strip(row.opcode)
|
|
&& compact_event_dispatch_add_building_descriptor_id(row.descriptor_id)
|
|
})
|
|
})
|
|
.map(|record| {
|
|
compact_event_dispatch_row_shape_family_from_summary_rows(
|
|
&record.grouped_effect_rows,
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
add_building_dispatch_strip_row_shape_families.sort_unstable();
|
|
add_building_dispatch_strip_row_shape_families.dedup();
|
|
let mut add_building_dispatch_strip_signature_families = records
|
|
.iter()
|
|
.filter(|record| {
|
|
record.grouped_effect_rows.iter().any(|row| {
|
|
opcode_reaches_world_apply_compact_runtime_effect_dispatch_strip(row.opcode)
|
|
&& compact_event_dispatch_add_building_descriptor_id(row.descriptor_id)
|
|
})
|
|
})
|
|
.map(|record| {
|
|
compact_event_signature_family_from_notes(&record.notes)
|
|
.unwrap_or_else(|| "unknown-signature-family".to_string())
|
|
})
|
|
.collect::<Vec<_>>();
|
|
add_building_dispatch_strip_signature_families.sort_unstable();
|
|
add_building_dispatch_strip_signature_families.dedup();
|
|
let mut add_building_dispatch_strip_condition_tuple_families = records
|
|
.iter()
|
|
.filter(|record| {
|
|
record.grouped_effect_rows.iter().any(|row| {
|
|
opcode_reaches_world_apply_compact_runtime_effect_dispatch_strip(row.opcode)
|
|
&& compact_event_dispatch_add_building_descriptor_id(row.descriptor_id)
|
|
})
|
|
})
|
|
.map(|record| {
|
|
compact_event_dispatch_condition_tuple_family_from_summary_rows(
|
|
&record.standalone_condition_rows,
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
add_building_dispatch_strip_condition_tuple_families.sort_unstable();
|
|
add_building_dispatch_strip_condition_tuple_families.dedup();
|
|
let mut add_building_dispatch_strip_signature_condition_clusters = records
|
|
.iter()
|
|
.filter(|record| {
|
|
record.grouped_effect_rows.iter().any(|row| {
|
|
opcode_reaches_world_apply_compact_runtime_effect_dispatch_strip(row.opcode)
|
|
&& compact_event_dispatch_add_building_descriptor_id(row.descriptor_id)
|
|
})
|
|
})
|
|
.map(|record| {
|
|
compact_event_dispatch_signature_condition_cluster_from_summary_rows(
|
|
compact_event_signature_family_from_notes(&record.notes).as_deref(),
|
|
&record.standalone_condition_rows,
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
add_building_dispatch_strip_signature_condition_clusters.sort_unstable();
|
|
add_building_dispatch_strip_signature_condition_clusters.dedup();
|
|
let mut mutation_candidate_unknown_descriptor_ids = records
|
|
.iter()
|
|
.flat_map(|record| record.grouped_effect_rows.iter())
|
|
.filter(|row| {
|
|
opcode_reaches_world_apply_compact_runtime_effect_dispatch_strip(row.opcode)
|
|
&& row.descriptor_label.is_none()
|
|
})
|
|
.map(|row| row.descriptor_id)
|
|
.collect::<Vec<_>>();
|
|
mutation_candidate_unknown_descriptor_ids.sort_unstable();
|
|
mutation_candidate_unknown_descriptor_ids.dedup();
|
|
let mut mutation_candidate_special_condition_label_matches =
|
|
mutation_candidate_unknown_descriptor_ids
|
|
.iter()
|
|
.filter_map(|descriptor_id| {
|
|
known_special_condition_label_for_compact_descriptor_id(*descriptor_id)
|
|
.map(|label| format!("{descriptor_id} -> {label}"))
|
|
})
|
|
.collect::<Vec<_>>();
|
|
mutation_candidate_special_condition_label_matches.sort();
|
|
mutation_candidate_special_condition_label_matches.dedup();
|
|
let mut dispatch_strip_unknown_condition_ids = records
|
|
.iter()
|
|
.filter(|record| {
|
|
record.grouped_effect_rows.iter().any(|row| {
|
|
opcode_reaches_world_apply_compact_runtime_effect_dispatch_strip(row.opcode)
|
|
})
|
|
})
|
|
.flat_map(|record| record.standalone_condition_rows.iter())
|
|
.filter(|row| row.raw_condition_id >= 0 && row.metric.is_none())
|
|
.map(|row| row.raw_condition_id)
|
|
.collect::<Vec<_>>();
|
|
dispatch_strip_unknown_condition_ids.sort_unstable();
|
|
dispatch_strip_unknown_condition_ids.dedup();
|
|
let mut control_lane_notes = Vec::new();
|
|
if nondirect_compact_record_count != 0
|
|
&& nondirect_compact_record_count == nondirect_compact_records_missing_trigger_kind
|
|
{
|
|
control_lane_notes.push(
|
|
"all compact non-direct rows currently decode row bodies only and still lack a decoded trigger/control lane".to_string(),
|
|
);
|
|
control_lane_notes.push(
|
|
"direct disassembly now grounds that as a real loader boundary: 0x0042db20 allocates linked 0x1e/0x28 row nodes from the 0x4e9a slice and leaves [event+0x7ee..0x80f] to the separate 0x0042e050 full-event clone path, so missing trigger-kind bytes on this family are not just a parser gap".to_string(),
|
|
);
|
|
control_lane_notes.push(
|
|
"the checked 0x0042e050 caller census is narrow too: current direct caller 0x004dba23 sits under the event-editor duplication path rather than 0x00433130 load, so ordinary 0x4e9a restore is not currently grounded to inherit trigger/control bytes through that deep-copy seam".to_string(),
|
|
);
|
|
control_lane_notes.push(
|
|
"the first non-editor positive control-lane writer is bounded away from ordinary restore too: 0x00430b50 allocates a fresh live runtime-effect row through 0x00432ea0 -> 0x0042d5a0, seeds [event+0x7ef] to 2 or 3 plus adjacent control bytes, and is reached from 0x004323a0 service at 0x0043232e rather than 0x00433130 load".to_string(),
|
|
);
|
|
control_lane_notes.push(
|
|
"the remaining non-editor [event+0x7ef] mutators are bounded away from restore too: the 0x00443200..0x004436e3 sweep searches existing live runtime-event names through 0x005a57cf (including strings like 'New Beginnings', 'Chicago to New York', 'The American', and 'Labor') and retags already-live records, so it reads as scenario-specific live maintenance rather than the missing 0x4e9a restore owner".to_string(),
|
|
);
|
|
control_lane_notes.push(
|
|
"direct disassembly of 0x004323a0 now makes the live gate explicit too: the per-record service returns before dispatch unless one-shot latch [event+0x81f] is clear, mode byte [event+0x7ef] matches the selected trigger kind from 0x00432f40, and compact chain root [event+0x00] is nonzero; its kind-8 side path at 0x00432ca1..0x00432cb0 only calls 0x00438710 on already-live records with [event+0x7ef] == 8".to_string(),
|
|
);
|
|
control_lane_notes.push(
|
|
"the post-load name-driven retagger is narrower than a bulk trigger-kind materializer too: direct disassembly of 0x00442c30 (called from 0x00443a50 at 0x00444b50) shows a hardcoded scenario-name patch table over already-live records in 0x0062be18/0x0062bae0, and the checked cases mostly tweak modifier bytes [event+0x7f9/+0x7fa] or nested payload scalars on records that already carry concrete kinds such as 7 ('Open Aus', 'The American'), 6 ('Test connections'), 5 ('Win - Gold'), and 1 ('Win - Silver' / 'Win - Bronze')".to_string(),
|
|
);
|
|
control_lane_notes.push(
|
|
"direct disassembly now boxes in the explicit trigger-kind materializations inside that same retagger too: the 'SP - GOLD' branch at 0x00443526 rewrites [event+0x7ef] from 1 to 5 on live runtime-event id 1 when the scenario flag [world+0x66de] is set and payload-root kind 7 carries subtype byte 5, while the 'Labor' branch at 0x00443601 rewrites [event+0x7ef] from 0 to 2 on live runtime-event id 0x0d when the same scenario flag is set and the checked 0x3c -> 0x3d child payload pair carries the matching negative scalar sentinel".to_string(),
|
|
);
|
|
}
|
|
if records_with_trigger_kind != 0 {
|
|
control_lane_notes.push(format!(
|
|
"decoded trigger kinds present in this collection = {:?}",
|
|
trigger_kinds_present
|
|
));
|
|
}
|
|
if !mutation_candidate_record_indexes.is_empty() {
|
|
control_lane_notes.push(format!(
|
|
"records with grouped opcodes already in the 0x00431b20 dispatch strip = {:?}",
|
|
mutation_candidate_record_indexes
|
|
));
|
|
if records_with_trigger_kind == 0 {
|
|
control_lane_notes.push(
|
|
"decoded grouped rows already reach the 0x00431b20 dispatch strip in this collection even though the current inspection surface recovered no trigger/control kind bytes for those records"
|
|
.to_string(),
|
|
);
|
|
if nondirect_compact_record_count == records.len() {
|
|
control_lane_notes.push(
|
|
"every currently decoded dispatch-strip row in this collection still sits in the nondirect compact 0x4e99/0x4e9a/0x4e9b family with null [event+0x7ef], so the direct full-record 0x4e21/0x4e22 framing path is not currently bridging trigger-kind control bytes for these mutation-capable rows".to_string(),
|
|
);
|
|
}
|
|
}
|
|
control_lane_notes.push(format!(
|
|
"0x00431b20 dispatch-strip opcodes present in decoded grouped rows = {:?}",
|
|
mutation_candidate_opcodes
|
|
));
|
|
if !mutation_candidate_descriptor_labels.is_empty() {
|
|
control_lane_notes.push(format!(
|
|
"decoded grouped descriptor labels present in the 0x00431b20 dispatch strip = {:?}",
|
|
mutation_candidate_descriptor_labels
|
|
));
|
|
}
|
|
if !mutation_candidate_unknown_descriptor_ids.is_empty() {
|
|
control_lane_notes.push(format!(
|
|
"grouped descriptor ids still missing checked-in labels in the 0x00431b20 dispatch strip = {:?}",
|
|
mutation_candidate_unknown_descriptor_ids
|
|
));
|
|
}
|
|
if !mutation_candidate_special_condition_label_matches.is_empty() {
|
|
control_lane_notes.push(format!(
|
|
"unlabeled 0x00431b20 dispatch-strip descriptor ids matching known special-condition label_id-2000 values = {:?}",
|
|
mutation_candidate_special_condition_label_matches
|
|
));
|
|
}
|
|
if !dispatch_strip_unknown_condition_ids.is_empty() {
|
|
control_lane_notes.push(format!(
|
|
"standalone condition ids still missing checked-in labels in the 0x00431b20 dispatch strip = {:?}",
|
|
dispatch_strip_unknown_condition_ids
|
|
));
|
|
}
|
|
if !opcode_0x08_record_indexes.is_empty() {
|
|
control_lane_notes.push(format!(
|
|
"records with opcode 0x08 in the 0x00431b20 dispatch strip = {:?}",
|
|
opcode_0x08_record_indexes
|
|
));
|
|
control_lane_notes.push(
|
|
"checked-in function-map evidence currently grounds opcode 0x08 on the 0x00426d60 company_deactivate_and_clear_chairman_share_links branch".to_string(),
|
|
);
|
|
}
|
|
if !add_building_dispatch_strip_record_indexes.is_empty() {
|
|
control_lane_notes.push(format!(
|
|
"records with Add Building descriptors in the 0x00431b20 dispatch strip = {:?}",
|
|
add_building_dispatch_strip_record_indexes
|
|
));
|
|
control_lane_notes.push(format!(
|
|
"decoded Add Building descriptor labels present in the 0x00431b20 dispatch strip = {:?}",
|
|
add_building_dispatch_strip_descriptor_labels
|
|
));
|
|
control_lane_notes.push(format!(
|
|
"Add Building row-shape families present in the 0x00431b20 dispatch strip = {:?}",
|
|
add_building_dispatch_strip_row_shape_families
|
|
));
|
|
control_lane_notes.push(format!(
|
|
"Add Building signature families present in the 0x00431b20 dispatch strip = {:?}",
|
|
add_building_dispatch_strip_signature_families
|
|
));
|
|
control_lane_notes.push(format!(
|
|
"Add Building condition-tuple families present in the 0x00431b20 dispatch strip = {:?}",
|
|
add_building_dispatch_strip_condition_tuple_families
|
|
));
|
|
control_lane_notes.push(format!(
|
|
"Add Building signature/condition clusters present in the 0x00431b20 dispatch strip = {:?}",
|
|
add_building_dispatch_strip_signature_condition_clusters
|
|
));
|
|
if add_building_dispatch_strip_records_with_trigger_kind == 0 {
|
|
control_lane_notes.push(
|
|
"every currently decoded Add Building dispatch-strip row still has null trigger kind, so the missing control-lane mapping remains the blocker above the already-grounded add-building descriptor bridge".to_string(),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return Some(SmpLoadedEventRuntimeCollectionSummary {
|
|
source_kind,
|
|
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_with_trigger_kind,
|
|
records_missing_trigger_kind,
|
|
nondirect_compact_record_count,
|
|
nondirect_compact_records_missing_trigger_kind,
|
|
trigger_kinds_present,
|
|
add_building_dispatch_strip_record_indexes,
|
|
add_building_dispatch_strip_descriptor_labels,
|
|
add_building_dispatch_strip_records_with_trigger_kind,
|
|
add_building_dispatch_strip_records_missing_trigger_kind,
|
|
add_building_dispatch_strip_row_shape_families,
|
|
add_building_dispatch_strip_signature_families,
|
|
add_building_dispatch_strip_condition_tuple_families,
|
|
add_building_dispatch_strip_signature_condition_clusters,
|
|
control_lane_notes,
|
|
records,
|
|
});
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn opcode_reaches_world_apply_compact_runtime_effect_dispatch_strip(opcode: u8) -> bool {
|
|
matches!(opcode, 0x04..=0x08 | 0x0d | 0x10..=0x13 | 0x16)
|
|
}
|
|
|
|
fn compact_event_dispatch_add_building_descriptor_id(descriptor_id: u32) -> bool {
|
|
(503..=613).contains(&descriptor_id)
|
|
}
|
|
|
|
fn compact_event_signature_family_from_notes(notes: &[String]) -> Option<String> {
|
|
notes.iter().find_map(|note| {
|
|
note.strip_prefix("compact signature family = ")
|
|
.map(ToString::to_string)
|
|
})
|
|
}
|
|
|
|
fn compact_event_dispatch_condition_tuple_family_from_summary_rows(
|
|
rows: &[SmpLoadedPackedEventConditionRowSummary],
|
|
) -> String {
|
|
if rows.is_empty() {
|
|
return "[]".to_string();
|
|
}
|
|
let parts = rows
|
|
.iter()
|
|
.map(|row| match &row.metric {
|
|
Some(metric) => format!("{}:{}:{}", row.raw_condition_id, row.subtype, metric),
|
|
None => format!("{}:{}", row.raw_condition_id, row.subtype),
|
|
})
|
|
.collect::<Vec<_>>();
|
|
format!("[{}]", parts.join(","))
|
|
}
|
|
|
|
fn compact_event_dispatch_row_shape_family_from_summary_rows(
|
|
rows: &[SmpLoadedPackedEventGroupedEffectRowSummary],
|
|
) -> String {
|
|
if rows.is_empty() {
|
|
return "[]".to_string();
|
|
}
|
|
let parts = rows
|
|
.iter()
|
|
.map(|row| {
|
|
format!(
|
|
"{}:{}:{}",
|
|
row.group_index, row.opcode, row.raw_scalar_value
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
format!("[{}]", parts.join(","))
|
|
}
|
|
|
|
fn compact_event_dispatch_signature_condition_cluster_from_summary_rows(
|
|
signature_family: Option<&str>,
|
|
rows: &[SmpLoadedPackedEventConditionRowSummary],
|
|
) -> String {
|
|
format!(
|
|
"{} :: {}",
|
|
signature_family.unwrap_or("unknown-signature-family"),
|
|
compact_event_dispatch_condition_tuple_family_from_summary_rows(rows)
|
|
)
|
|
}
|
|
|
|
fn known_special_condition_label_for_compact_descriptor_id(
|
|
descriptor_id: u32,
|
|
) -> Option<&'static str> {
|
|
let label_id = descriptor_id.checked_add(2000)?;
|
|
KNOWN_SPECIAL_CONDITION_DEFINITIONS
|
|
.iter()
|
|
.find(|definition| definition.label_id == label_id)
|
|
.map(|definition| definition.label)
|
|
}
|
|
|
|
fn try_parse_nondirect_event_runtime_record_summaries(
|
|
records_payload: &[u8],
|
|
records_payload_offset: usize,
|
|
live_entry_ids: &[u32],
|
|
) -> Option<Vec<SmpLoadedPackedEventRecordSummary>> {
|
|
let marker_offsets =
|
|
find_u32_le_offsets(records_payload, PACKED_EVENT_REAL_CONDITION_MARKER as u32);
|
|
if marker_offsets.len() != live_entry_ids.len() || marker_offsets.first().copied() != Some(0) {
|
|
return None;
|
|
}
|
|
|
|
let mut record_offsets = marker_offsets;
|
|
record_offsets.push(records_payload.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 start = *record_offsets.get(record_index)?;
|
|
let end = *record_offsets.get(record_index + 1)?;
|
|
let record_body = records_payload.get(start..end)?;
|
|
let record = parse_nondirect_event_runtime_record_summary(
|
|
record_body,
|
|
records_payload_offset + start,
|
|
record_index,
|
|
live_entry_id,
|
|
)
|
|
.or_else(|| {
|
|
build_nondirect_event_runtime_record_summary_from_signatures(
|
|
record_body,
|
|
records_payload_offset + start,
|
|
record_index,
|
|
live_entry_id,
|
|
)
|
|
})?;
|
|
records.push(record);
|
|
}
|
|
|
|
Some(records)
|
|
}
|
|
|
|
fn parse_nondirect_event_runtime_record_summary(
|
|
record_body: &[u8],
|
|
payload_offset: usize,
|
|
record_index: usize,
|
|
live_entry_id: u32,
|
|
) -> Option<SmpLoadedPackedEventRecordSummary> {
|
|
let mut cursor = 0usize;
|
|
if read_u32_at(record_body, cursor)? != PACKED_EVENT_REAL_CONDITION_MARKER as u32 {
|
|
return None;
|
|
}
|
|
cursor += 4;
|
|
|
|
let standalone_condition_row_count = usize::try_from(read_u32_at(record_body, cursor)?).ok()?;
|
|
cursor += 4;
|
|
let mut standalone_condition_rows = Vec::with_capacity(standalone_condition_row_count);
|
|
for row_index in 0..standalone_condition_row_count {
|
|
let remaining_minimum = standalone_condition_row_count
|
|
.checked_sub(row_index + 1)?
|
|
.checked_mul(PACKED_EVENT_NONDIRECT_CONDITION_ROW_SERIALIZED_LEN)?
|
|
.checked_add(4)?
|
|
.checked_add(PACKED_EVENT_REAL_GROUP_COUNT.checked_mul(4)?)?
|
|
.checked_add(4)?;
|
|
let (row, consumed_len) = parse_nondirect_condition_row_summary(
|
|
record_body.get(cursor..)?,
|
|
row_index,
|
|
remaining_minimum,
|
|
)?;
|
|
standalone_condition_rows.push(row);
|
|
cursor += consumed_len;
|
|
}
|
|
|
|
let grouped_marker_relative_offset = cursor;
|
|
if read_u32_at(record_body, cursor)? != PACKED_EVENT_REAL_GROUPED_EFFECT_MARKER as u32 {
|
|
return None;
|
|
}
|
|
cursor += 4;
|
|
|
|
let mut grouped_effect_row_counts = Vec::with_capacity(PACKED_EVENT_REAL_GROUP_COUNT);
|
|
let mut grouped_effect_rows = Vec::new();
|
|
for group_index in 0..PACKED_EVENT_REAL_GROUP_COUNT {
|
|
let group_row_count = usize::try_from(read_u32_at(record_body, cursor)?).ok()?;
|
|
cursor += 4;
|
|
grouped_effect_row_counts.push(group_row_count);
|
|
for row_index in 0..group_row_count {
|
|
let remaining_groups_minimum = (group_row_count - row_index - 1)
|
|
.checked_mul(PACKED_EVENT_NONDIRECT_GROUPED_EFFECT_ROW_SERIALIZED_LEN)?
|
|
.checked_add((PACKED_EVENT_REAL_GROUP_COUNT - group_index - 1).checked_mul(4)?)?
|
|
.checked_add(4)?;
|
|
let (row, consumed_len) = parse_nondirect_grouped_effect_row_summary(
|
|
record_body.get(cursor..)?,
|
|
group_index,
|
|
row_index,
|
|
remaining_groups_minimum,
|
|
)?;
|
|
grouped_effect_rows.push(row);
|
|
cursor += consumed_len;
|
|
}
|
|
}
|
|
|
|
let end_marker_relative_offset = cursor;
|
|
if read_u32_at(record_body, cursor)? != PACKED_EVENT_REAL_RECORD_TERMINATOR_MARKER as u32 {
|
|
return None;
|
|
}
|
|
cursor += 4;
|
|
if cursor != record_body.len() {
|
|
return None;
|
|
}
|
|
|
|
let head_signature_words = read_u16_window(record_body, 0, 18);
|
|
let post_group_signature_words =
|
|
read_u16_window(record_body, grouped_marker_relative_offset + 4, 12);
|
|
let ascii_preview_before_grouped_marker = record_body
|
|
.get(..grouped_marker_relative_offset)
|
|
.map(ascii_preview);
|
|
|
|
let mut notes = vec![
|
|
"decoded from compact non-direct 0x4e99/0x4e9a/0x4e9b map-bundle row framing recovered from the paired 0x433060/0x430d70 writer strip and 0x433130/0x42db20 loader strip".to_string(),
|
|
format!(
|
|
"compact signature family = {}",
|
|
compact_nondirect_signature_family(
|
|
Some(grouped_marker_relative_offset),
|
|
&head_signature_words,
|
|
&post_group_signature_words,
|
|
)
|
|
),
|
|
format!(
|
|
"head signature u16 words = {}",
|
|
format_u16_word_signature(&head_signature_words)
|
|
),
|
|
format!(
|
|
"grouped-effect marker 0x4eb8 at relative offset +0x{grouped_marker_relative_offset:x}"
|
|
),
|
|
format!(
|
|
"row terminator marker 0x4eb9 at relative offset +0x{end_marker_relative_offset:x}"
|
|
),
|
|
];
|
|
if !post_group_signature_words.is_empty() {
|
|
notes.push(format!(
|
|
"post-group signature u16 words = {}",
|
|
format_u16_word_signature(&post_group_signature_words)
|
|
));
|
|
}
|
|
if let Some(preview) = ascii_preview_before_grouped_marker {
|
|
notes.push(format!("ascii preview before grouped marker = {preview}"));
|
|
}
|
|
notes.push(format!(
|
|
"compact non-direct grouped row counts by group = {:?}",
|
|
grouped_effect_row_counts
|
|
));
|
|
notes.push(
|
|
"the compact non-direct row body reconstructs standalone/grouped rows only; the separate 0x42e050 full-event clone helper is the nearby owner that copies text bands plus control lane [event+0x7ee..0x80f] between live runtime-event rows".to_string(),
|
|
);
|
|
notes.push(
|
|
"direct disassembly of 0x0042db20 now grounds that absence too: this loader allocates 0x1e-byte standalone-condition nodes and 0x28-byte grouped-row nodes from the 0x4e9a slice, but does not materialize the compact control lane [event+0x7ee..0x80f] or a trigger-kind byte for this non-direct row family".to_string(),
|
|
);
|
|
notes.push(
|
|
"the adjacent deep-copy seam is bounded too: current direct caller census shows 0x0042e050 reached from editor duplication at 0x004dba23, not from the ordinary 0x00433130 load path, so this non-direct row family is not currently grounded to inherit trigger/control bytes during restore through that helper".to_string(),
|
|
);
|
|
notes.push(
|
|
"the first non-editor positive control-lane writer is bounded away from ordinary restore too: 0x00430b50 allocates a fresh live runtime-effect row through 0x00432ea0 -> 0x0042d5a0, seeds [event+0x7ef] to 2 or 3 plus adjacent control bytes, and is only reached from the 0x004323a0 follow-on service strip rather than the 0x00433130 nondirect load path".to_string(),
|
|
);
|
|
|
|
let decoded_conditions = decode_real_condition_rows(&standalone_condition_rows, None);
|
|
|
|
Some(SmpLoadedPackedEventRecordSummary {
|
|
record_index,
|
|
live_entry_id,
|
|
payload_offset: Some(payload_offset),
|
|
payload_len: Some(cursor),
|
|
decode_status: "parity_only".to_string(),
|
|
payload_family: "real_packed_nondirect_compact_v1".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,
|
|
standalone_condition_rows,
|
|
negative_sentinel_scope: None,
|
|
grouped_effect_row_counts,
|
|
grouped_effect_rows,
|
|
decoded_conditions,
|
|
decoded_actions: Vec::new(),
|
|
executable_import_ready: false,
|
|
notes,
|
|
})
|
|
}
|
|
|
|
fn build_nondirect_event_runtime_record_summary_from_signatures(
|
|
record_body: &[u8],
|
|
payload_offset: usize,
|
|
record_index: usize,
|
|
live_entry_id: u32,
|
|
) -> Option<SmpLoadedPackedEventRecordSummary> {
|
|
let grouped_marker_relative_offset =
|
|
find_u32_le_offsets(record_body, PACKED_EVENT_REAL_GROUPED_EFFECT_MARKER as u32)
|
|
.into_iter()
|
|
.next();
|
|
let end_marker_relative_offset = find_u32_le_offsets(
|
|
record_body,
|
|
PACKED_EVENT_REAL_RECORD_TERMINATOR_MARKER as u32,
|
|
)
|
|
.into_iter()
|
|
.next();
|
|
let head_signature_words = read_u16_window(record_body, 0, 18);
|
|
let post_group_signature_words = grouped_marker_relative_offset
|
|
.map(|offset| offset + 4)
|
|
.map(|offset| read_u16_window(record_body, offset, 12))
|
|
.unwrap_or_default();
|
|
let ascii_preview_before_grouped_marker = grouped_marker_relative_offset
|
|
.and_then(|offset| record_body.get(..offset).map(ascii_preview));
|
|
|
|
let mut notes = vec![
|
|
"decoded from non-direct 0x4e99/0x4e9a/0x4e9b map-bundle row segmentation using 0x526f-delimited slices".to_string(),
|
|
format!(
|
|
"compact signature family = {}",
|
|
compact_nondirect_signature_family(
|
|
grouped_marker_relative_offset,
|
|
&head_signature_words,
|
|
&post_group_signature_words,
|
|
)
|
|
),
|
|
format!(
|
|
"head signature u16 words = {}",
|
|
format_u16_word_signature(&head_signature_words)
|
|
),
|
|
];
|
|
if let Some(offset) = grouped_marker_relative_offset {
|
|
notes.push(format!(
|
|
"grouped-effect marker 0x4eb8 at relative offset +0x{offset:x}"
|
|
));
|
|
if !post_group_signature_words.is_empty() {
|
|
notes.push(format!(
|
|
"post-group signature u16 words = {}",
|
|
format_u16_word_signature(&post_group_signature_words)
|
|
));
|
|
}
|
|
}
|
|
if let Some(offset) = end_marker_relative_offset {
|
|
notes.push(format!(
|
|
"row terminator marker 0x4eb9 at relative offset +0x{offset:x}"
|
|
));
|
|
}
|
|
if let Some(preview) = ascii_preview_before_grouped_marker {
|
|
notes.push(format!("ascii preview before grouped marker = {preview}"));
|
|
}
|
|
|
|
Some(SmpLoadedPackedEventRecordSummary {
|
|
record_index,
|
|
live_entry_id,
|
|
payload_offset: Some(payload_offset),
|
|
payload_len: Some(record_body.len()),
|
|
decode_status: "compact_nondirect_parity_only".to_string(),
|
|
payload_family: "real_packed_nondirect_compact_v1".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,
|
|
})
|
|
}
|
|
|
|
fn parse_nondirect_condition_row_summary(
|
|
record_body: &[u8],
|
|
row_index: usize,
|
|
remaining_minimum: usize,
|
|
) -> Option<(SmpLoadedPackedEventConditionRowSummary, usize)> {
|
|
let mut cursor = 0usize;
|
|
let mut row_bytes = vec![0u8; PACKED_EVENT_REAL_CONDITION_ROW_LEN];
|
|
row_bytes
|
|
.get_mut(0..4)?
|
|
.copy_from_slice(record_body.get(cursor..cursor + 4)?);
|
|
cursor += 4;
|
|
row_bytes[4] = read_u8_at(record_body, cursor)?;
|
|
cursor += 1;
|
|
row_bytes
|
|
.get_mut(5..9)?
|
|
.copy_from_slice(record_body.get(cursor..cursor + 4)?);
|
|
cursor += 4;
|
|
row_bytes
|
|
.get_mut(9..13)?
|
|
.copy_from_slice(record_body.get(cursor..cursor + 4)?);
|
|
cursor += 4;
|
|
row_bytes[0x0d] = read_u8_at(record_body, cursor)?;
|
|
cursor += 1;
|
|
row_bytes
|
|
.get_mut(0x0e..0x12)?
|
|
.copy_from_slice(record_body.get(cursor..cursor + 4)?);
|
|
cursor += 4;
|
|
row_bytes
|
|
.get_mut(0x12..0x16)?
|
|
.copy_from_slice(record_body.get(cursor..cursor + 4)?);
|
|
cursor += 4;
|
|
let candidate_name =
|
|
maybe_parse_nondirect_optional_name_block(record_body, &mut cursor, remaining_minimum)?;
|
|
let mut row = parse_real_condition_row_summary(&row_bytes, row_index, candidate_name)?;
|
|
row.notes.push(
|
|
"condition row reconstructed from the compact non-direct serializer fields under 0x430e80"
|
|
.to_string(),
|
|
);
|
|
Some((row, cursor))
|
|
}
|
|
|
|
fn parse_nondirect_grouped_effect_row_summary(
|
|
record_body: &[u8],
|
|
group_index: usize,
|
|
row_index: usize,
|
|
remaining_minimum: usize,
|
|
) -> Option<(SmpLoadedPackedEventGroupedEffectRowSummary, usize)> {
|
|
let mut cursor = 0usize;
|
|
let mut row_bytes = vec![0u8; PACKED_EVENT_REAL_GROUPED_EFFECT_ROW_LEN];
|
|
row_bytes
|
|
.get_mut(0..4)?
|
|
.copy_from_slice(record_body.get(cursor..cursor + 4)?);
|
|
cursor += 4;
|
|
row_bytes
|
|
.get_mut(4..8)?
|
|
.copy_from_slice(record_body.get(cursor..cursor + 4)?);
|
|
cursor += 4;
|
|
row_bytes[8] = read_u8_at(record_body, cursor)?;
|
|
cursor += 1;
|
|
row_bytes
|
|
.get_mut(9..13)?
|
|
.copy_from_slice(record_body.get(cursor..cursor + 4)?);
|
|
cursor += 4;
|
|
row_bytes
|
|
.get_mut(0x0d..0x11)?
|
|
.copy_from_slice(record_body.get(cursor..cursor + 4)?);
|
|
cursor += 4;
|
|
row_bytes
|
|
.get_mut(0x11..0x15)?
|
|
.copy_from_slice(record_body.get(cursor..cursor + 4)?);
|
|
cursor += 4;
|
|
row_bytes
|
|
.get_mut(0x12..0x16)?
|
|
.copy_from_slice(record_body.get(cursor..cursor + 4)?);
|
|
cursor += 4;
|
|
row_bytes
|
|
.get_mut(0x14..0x18)?
|
|
.copy_from_slice(record_body.get(cursor..cursor + 4)?);
|
|
cursor += 4;
|
|
row_bytes
|
|
.get_mut(0x16..0x1a)?
|
|
.copy_from_slice(record_body.get(cursor..cursor + 4)?);
|
|
cursor += 4;
|
|
row_bytes
|
|
.get_mut(0x18..0x24)?
|
|
.copy_from_slice(record_body.get(cursor..cursor + 12)?);
|
|
cursor += 12;
|
|
let locomotive_name =
|
|
maybe_parse_nondirect_optional_name_block(record_body, &mut cursor, remaining_minimum)?;
|
|
let mut row =
|
|
parse_real_grouped_effect_row_summary(&row_bytes, group_index, row_index, locomotive_name)?;
|
|
row.notes.push(
|
|
"grouped effect row reconstructed from the compact non-direct serializer fields under 0x430f68"
|
|
.to_string(),
|
|
);
|
|
Some((row, cursor))
|
|
}
|
|
|
|
fn maybe_parse_nondirect_optional_name_block(
|
|
record_body: &[u8],
|
|
cursor: &mut usize,
|
|
remaining_minimum: usize,
|
|
) -> Option<Option<String>> {
|
|
if record_body.len() < *cursor + PACKED_EVENT_NONDIRECT_OPTIONAL_NAME_BLOCK_LEN {
|
|
return Some(None);
|
|
}
|
|
if record_body.len()
|
|
< *cursor + PACKED_EVENT_NONDIRECT_OPTIONAL_NAME_BLOCK_LEN + remaining_minimum
|
|
{
|
|
return Some(None);
|
|
}
|
|
let block =
|
|
record_body.get(*cursor..*cursor + PACKED_EVENT_NONDIRECT_OPTIONAL_NAME_BLOCK_LEN)?;
|
|
let name = read_ascii_c_string_at(block, 0, PACKED_EVENT_NONDIRECT_OPTIONAL_NAME_BLOCK_LEN);
|
|
let Some(name) = name.filter(|name| {
|
|
!name.is_empty()
|
|
&& block
|
|
.iter()
|
|
.copied()
|
|
.all(|byte| byte == 0 || is_ascii_preview_byte(byte))
|
|
}) else {
|
|
return Some(None);
|
|
};
|
|
*cursor += PACKED_EVENT_NONDIRECT_OPTIONAL_NAME_BLOCK_LEN;
|
|
Some(Some(name))
|
|
}
|
|
|
|
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();
|
|
}
|
|
if descriptor_metadata
|
|
.is_some_and(|metadata| metadata.parameter_family == "world_building_spawn")
|
|
{
|
|
row_shape = "building_spawn_batch".to_string();
|
|
semantic_family = "building_spawn_batch".to_string();
|
|
}
|
|
|
|
let mut notes = Vec::new();
|
|
if locomotive_name.is_some() {
|
|
notes.push("grouped effect row carries locomotive-name side string".to_string());
|
|
}
|
|
if let Some(metadata) = descriptor_metadata {
|
|
if metadata.runtime_status != RealGroupedEffectRuntimeStatus::Executable {
|
|
notes.push(format!(
|
|
"descriptor is recovered in the checked-in effect table as {} parity",
|
|
real_grouped_effect_runtime_status_name(metadata.runtime_status)
|
|
));
|
|
}
|
|
} else {
|
|
notes.push("descriptor id not yet recovered in the checked-in effect table".to_string());
|
|
}
|
|
if let Some(loco_id) = recovered_locomotive_availability_loco_id(descriptor_id) {
|
|
notes.push(format!(
|
|
"locomotive availability descriptor maps to live locomotive id {loco_id}"
|
|
));
|
|
}
|
|
if let Some(loco_id) = recovered_locomotive_cost_loco_id(descriptor_id) {
|
|
notes.push(format!(
|
|
"locomotive cost descriptor maps to live locomotive id {loco_id}"
|
|
));
|
|
}
|
|
if let Some(cargo_slot) = recovered_cargo_production_slot(descriptor_id) {
|
|
notes.push(format!(
|
|
"cargo-production descriptor maps to world production slot {cargo_slot}"
|
|
));
|
|
}
|
|
if let Some(cargo_label) = grounded_named_cargo_production_label(descriptor_id) {
|
|
notes.push(format!(
|
|
"named cargo production descriptor maps to cargo {cargo_label}"
|
|
));
|
|
}
|
|
if descriptor_metadata.is_some_and(|metadata| metadata.parameter_family == "cargo_price_scalar")
|
|
{
|
|
if let Some(cargo_label) = grounded_named_cargo_price_label(descriptor_id) {
|
|
notes.push(format!(
|
|
"named cargo price descriptor maps to cargo {cargo_label}"
|
|
));
|
|
}
|
|
}
|
|
if descriptor_metadata
|
|
.is_some_and(|metadata| metadata.parameter_family == "world_building_spawn")
|
|
{
|
|
let candidate_id = descriptor_id.saturating_sub(503);
|
|
notes.push(format!(
|
|
"add-building descriptor maps to live candidate id {candidate_id}"
|
|
));
|
|
notes.push(
|
|
"0x430270 add-building consumer uses placement count byte 0x11, center words 0x12/0x14, and radius word 0x16 after the descriptor_id - 503 candidate bridge; it clamps radius to at least 1 and retries up to 200 randomized placements without branching directly on grouped opcode".to_string(),
|
|
);
|
|
if candidate_id > 66 {
|
|
notes.push(
|
|
"current non-hook candidate-name catalogs only ground concrete add-building names through candidate id 66, so this descriptor remains on the checked-in candidate-slot boundary beyond the live RT3 1.05 table".to_string(),
|
|
);
|
|
}
|
|
}
|
|
|
|
Some(SmpLoadedPackedEventGroupedEffectRowSummary {
|
|
group_index,
|
|
row_index,
|
|
descriptor_id,
|
|
descriptor_label: descriptor_metadata.map(|metadata| metadata.label.to_string()),
|
|
target_mask_bits: descriptor_metadata.map(|metadata| metadata.target_mask_bits),
|
|
parameter_family: descriptor_metadata.map(|metadata| metadata.parameter_family.to_string()),
|
|
grouped_target_subject: None,
|
|
grouped_target_scope: None,
|
|
opcode,
|
|
raw_scalar_value,
|
|
value_byte_0x09,
|
|
value_dword_0x0d,
|
|
value_byte_0x11,
|
|
value_byte_0x12,
|
|
value_word_0x14,
|
|
value_word_0x16,
|
|
row_shape,
|
|
semantic_family: Some(semantic_family.clone()),
|
|
semantic_preview: Some(build_real_grouped_effect_semantic_preview(
|
|
descriptor_metadata.map(|metadata| metadata.label),
|
|
&semantic_family,
|
|
raw_scalar_value,
|
|
value_byte_0x11,
|
|
value_byte_0x12,
|
|
value_word_0x14,
|
|
value_word_0x16,
|
|
)),
|
|
recovered_cargo_slot: recovered_cargo_production_slot(descriptor_id),
|
|
recovered_cargo_class: recovered_cargo_production_slot(descriptor_id)
|
|
.and_then(known_cargo_slot_definition)
|
|
.map(|definition| runtime_cargo_class_name(definition.cargo_class).to_string()),
|
|
recovered_cargo_label: grounded_named_cargo_production_label(descriptor_id)
|
|
.or_else(|| {
|
|
descriptor_metadata
|
|
.filter(|metadata| metadata.parameter_family == "cargo_price_scalar")
|
|
.and_then(|_| grounded_named_cargo_price_label(descriptor_id))
|
|
})
|
|
.map(ToString::to_string),
|
|
recovered_locomotive_id: recovered_locomotive_availability_loco_id(descriptor_id)
|
|
.or_else(|| recovered_locomotive_cost_loco_id(descriptor_id)),
|
|
locomotive_name,
|
|
notes,
|
|
})
|
|
}
|
|
|
|
fn decode_real_condition_rows(
|
|
rows: &[SmpLoadedPackedEventConditionRowSummary],
|
|
negative_sentinel_scope: Option<&SmpLoadedPackedEventNegativeSentinelScopeSummary>,
|
|
) -> Vec<RuntimeCondition> {
|
|
rows.iter()
|
|
.filter(|row| row.raw_condition_id >= 0)
|
|
.filter_map(|row| decode_real_condition_row(row, negative_sentinel_scope))
|
|
.collect()
|
|
}
|
|
|
|
fn decode_real_condition_row(
|
|
row: &SmpLoadedPackedEventConditionRowSummary,
|
|
negative_sentinel_scope: Option<&SmpLoadedPackedEventNegativeSentinelScopeSummary>,
|
|
) -> Option<RuntimeCondition> {
|
|
let metadata = real_ordinary_condition_metadata(row.raw_condition_id)?;
|
|
let comparator = decode_real_condition_comparator(row.subtype)?;
|
|
let value = decode_real_condition_threshold(&row.flag_bytes)?;
|
|
match metadata.kind {
|
|
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::WorldVariable(index)) => {
|
|
Some(RuntimeCondition::WorldVariableThreshold {
|
|
index,
|
|
comparator,
|
|
value,
|
|
})
|
|
}
|
|
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Company(metric)) => {
|
|
Some(RuntimeCondition::CompanyNumericThreshold {
|
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
|
metric,
|
|
comparator,
|
|
value,
|
|
})
|
|
}
|
|
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyVariable(index)) => {
|
|
Some(RuntimeCondition::CompanyVariableThreshold {
|
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
|
index,
|
|
comparator,
|
|
value,
|
|
})
|
|
}
|
|
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::PlayerVariable(index)) => {
|
|
negative_sentinel_scope.and_then(|scope| {
|
|
real_condition_player_target(scope).map(|target| {
|
|
RuntimeCondition::PlayerVariableThreshold {
|
|
target,
|
|
index,
|
|
comparator,
|
|
value,
|
|
}
|
|
})
|
|
})
|
|
}
|
|
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Chairman(metric)) => {
|
|
negative_sentinel_scope.and_then(|scope| {
|
|
real_condition_chairman_target(scope).map(|target| {
|
|
RuntimeCondition::ChairmanNumericThreshold {
|
|
target,
|
|
metric,
|
|
comparator,
|
|
value,
|
|
}
|
|
})
|
|
})
|
|
}
|
|
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::TerritoryVariable(
|
|
index,
|
|
)) => negative_sentinel_scope
|
|
.filter(|scope| scope.territory_scope_selector_is_0x63)
|
|
.map(|_| RuntimeCondition::TerritoryVariableThreshold {
|
|
target: RuntimeTerritoryTarget::AllTerritories,
|
|
index,
|
|
comparator,
|
|
value,
|
|
}),
|
|
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::Territory(metric)) => {
|
|
negative_sentinel_scope
|
|
.filter(|scope| scope.territory_scope_selector_is_0x63)
|
|
.map(|_| RuntimeCondition::TerritoryNumericThreshold {
|
|
target: RuntimeTerritoryTarget::AllTerritories,
|
|
metric,
|
|
comparator,
|
|
value,
|
|
})
|
|
}
|
|
RealOrdinaryConditionKind::Numeric(RealOrdinaryConditionMetric::CompanyTerritory(
|
|
metric,
|
|
)) => negative_sentinel_scope
|
|
.filter(|scope| scope.territory_scope_selector_is_0x63)
|
|
.map(|_| RuntimeCondition::CompanyTerritoryNumericThreshold {
|
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
|
territory: RuntimeTerritoryTarget::AllTerritories,
|
|
metric,
|
|
comparator,
|
|
value,
|
|
}),
|
|
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::SpecialCondition {
|
|
label,
|
|
}) => Some(RuntimeCondition::SpecialConditionThreshold {
|
|
label: label.to_string(),
|
|
comparator,
|
|
value,
|
|
}),
|
|
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CandidateAvailability) => row
|
|
.candidate_name
|
|
.as_ref()
|
|
.map(|name| RuntimeCondition::CandidateAvailabilityThreshold {
|
|
name: name.clone(),
|
|
comparator,
|
|
value,
|
|
}),
|
|
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CargoProductionSlot) => {
|
|
row.candidate_name.as_ref().and_then(|name| {
|
|
recovered_cargo_production_slot_from_condition_name(name).map(|slot| {
|
|
let label = known_cargo_slot_definition(slot)
|
|
.map(|definition| definition.label.to_string())
|
|
.unwrap_or_else(|| name.clone());
|
|
RuntimeCondition::CargoProductionSlotThreshold {
|
|
slot,
|
|
label,
|
|
comparator,
|
|
value,
|
|
}
|
|
})
|
|
})
|
|
}
|
|
RealOrdinaryConditionKind::WorldState(
|
|
RealWorldConditionKind::NamedLocomotiveAvailability,
|
|
) => row.candidate_name.as_ref().map(|name| {
|
|
RuntimeCondition::NamedLocomotiveAvailabilityThreshold {
|
|
name: name.clone(),
|
|
comparator,
|
|
value,
|
|
}
|
|
}),
|
|
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::NamedLocomotiveCost) => row
|
|
.candidate_name
|
|
.as_ref()
|
|
.map(|name| RuntimeCondition::NamedLocomotiveCostThreshold {
|
|
name: name.clone(),
|
|
comparator,
|
|
value,
|
|
}),
|
|
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::CargoProductionTotal) => {
|
|
Some(RuntimeCondition::CargoProductionTotalThreshold { comparator, value })
|
|
}
|
|
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::FactoryProductionTotal) => {
|
|
Some(RuntimeCondition::FactoryProductionTotalThreshold { comparator, value })
|
|
}
|
|
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::FarmMineProductionTotal) => {
|
|
Some(RuntimeCondition::FarmMineProductionTotalThreshold { comparator, value })
|
|
}
|
|
RealOrdinaryConditionKind::WorldState(
|
|
RealWorldConditionKind::OtherCargoProductionTotal,
|
|
) => Some(RuntimeCondition::OtherCargoProductionTotalThreshold { comparator, value }),
|
|
RealOrdinaryConditionKind::WorldState(
|
|
RealWorldConditionKind::LimitedTrackBuildingAmount,
|
|
) => Some(RuntimeCondition::LimitedTrackBuildingAmountThreshold { comparator, value }),
|
|
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::TerritoryAccessCost) => {
|
|
Some(RuntimeCondition::TerritoryAccessCostThreshold { comparator, value })
|
|
}
|
|
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::EconomicStatus) => {
|
|
Some(RuntimeCondition::EconomicStatusCodeThreshold { comparator, value })
|
|
}
|
|
RealOrdinaryConditionKind::WorldState(RealWorldConditionKind::WorldFlag { key }) => {
|
|
decode_world_flag_condition(comparator, value, key)
|
|
}
|
|
}
|
|
}
|
|
|
|
fn decode_world_flag_condition(
|
|
comparator: RuntimeConditionComparator,
|
|
value: i64,
|
|
key: &'static str,
|
|
) -> Option<RuntimeCondition> {
|
|
let bool_value = match (comparator, value) {
|
|
(RuntimeConditionComparator::Eq, 0) | (RuntimeConditionComparator::Ne, 1) => false,
|
|
(RuntimeConditionComparator::Eq, 1) | (RuntimeConditionComparator::Ne, 0) => true,
|
|
_ => return None,
|
|
};
|
|
Some(RuntimeCondition::WorldFlagEquals {
|
|
key: key.to_string(),
|
|
value: bool_value,
|
|
})
|
|
}
|
|
|
|
fn real_condition_chairman_target(
|
|
scope: &SmpLoadedPackedEventNegativeSentinelScopeSummary,
|
|
) -> Option<RuntimeChairmanTarget> {
|
|
match scope.player_test_scope {
|
|
RuntimePlayerConditionTestScope::AllPlayers => Some(RuntimeChairmanTarget::AllActive),
|
|
RuntimePlayerConditionTestScope::SelectedPlayerOnly => {
|
|
Some(RuntimeChairmanTarget::SelectedChairman)
|
|
}
|
|
RuntimePlayerConditionTestScope::AiPlayersOnly => Some(RuntimeChairmanTarget::AiChairmen),
|
|
RuntimePlayerConditionTestScope::HumanPlayersOnly => {
|
|
Some(RuntimeChairmanTarget::HumanChairmen)
|
|
}
|
|
RuntimePlayerConditionTestScope::Disabled => None,
|
|
}
|
|
}
|
|
|
|
fn real_condition_player_target(
|
|
scope: &SmpLoadedPackedEventNegativeSentinelScopeSummary,
|
|
) -> Option<RuntimePlayerTarget> {
|
|
match scope.player_test_scope {
|
|
RuntimePlayerConditionTestScope::AllPlayers => Some(RuntimePlayerTarget::AllActive),
|
|
RuntimePlayerConditionTestScope::SelectedPlayerOnly => {
|
|
Some(RuntimePlayerTarget::SelectedPlayer)
|
|
}
|
|
RuntimePlayerConditionTestScope::AiPlayersOnly => Some(RuntimePlayerTarget::AiPlayers),
|
|
RuntimePlayerConditionTestScope::HumanPlayersOnly => {
|
|
Some(RuntimePlayerTarget::HumanPlayers)
|
|
}
|
|
RuntimePlayerConditionTestScope::Disabled => None,
|
|
}
|
|
}
|
|
|
|
fn real_grouped_effect_descriptor_metadata(
|
|
descriptor_id: u32,
|
|
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
|
recovered_cargo_price_descriptor_metadata(descriptor_id)
|
|
.or_else(|| recovered_cargo_economics_descriptor_metadata(descriptor_id))
|
|
.or_else(|| recovered_cargo_production_descriptor_metadata(descriptor_id))
|
|
.or_else(|| recovered_locomotive_availability_descriptor_metadata(descriptor_id))
|
|
.or_else(|| recovered_locomotive_cost_descriptor_metadata(descriptor_id))
|
|
.or_else(|| recovered_territory_access_cost_descriptor_metadata(descriptor_id))
|
|
.or_else(|| recovered_locomotive_policy_descriptor_metadata(descriptor_id))
|
|
.or_else(|| special_condition_world_scalar_descriptor_metadata(descriptor_id))
|
|
.or_else(|| special_condition_world_toggle_descriptor_metadata(descriptor_id))
|
|
.or_else(|| {
|
|
REAL_GROUPED_EFFECT_DESCRIPTOR_METADATA
|
|
.iter()
|
|
.copied()
|
|
.find(|metadata| metadata.descriptor_id == descriptor_id)
|
|
})
|
|
.or_else(|| {
|
|
checked_in_event_effect_descriptor_rows()
|
|
.get(&descriptor_id)
|
|
.copied()
|
|
})
|
|
}
|
|
|
|
fn recovered_cargo_price_descriptor_metadata(
|
|
descriptor_id: u32,
|
|
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
|
(descriptor_id == 105).then_some(RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id,
|
|
label: "All Cargo Prices",
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "cargo_price_scalar",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
})
|
|
}
|
|
|
|
fn recovered_cargo_economics_descriptor_metadata(
|
|
descriptor_id: u32,
|
|
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
|
match descriptor_id {
|
|
177 => Some(RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id,
|
|
label: "All Cargo Production",
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "cargo_production_scalar",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
}),
|
|
178 => Some(RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id,
|
|
label: "All Factory Production",
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "cargo_production_scalar",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
}),
|
|
179 => Some(RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id,
|
|
label: "All Farm/Mine Production",
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "cargo_production_scalar",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
}),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
const GROUNDED_NAMED_CARGO_PRODUCTION_LABELS: [(&str, &str); 50] = [
|
|
("Alcohol", "Alcohol Production"),
|
|
("Aluminum", "Aluminum Production"),
|
|
("Ammunition", "Ammunition Production"),
|
|
("Automobiles", "Automobiles Production"),
|
|
("Bauxite", "Bauxite Production"),
|
|
("Ceramics", "Ceramics Production"),
|
|
("Cheese", "Cheese Production"),
|
|
("Chemicals", "Chemicals Production"),
|
|
("Clothing", "Clothing Production"),
|
|
("Coal", "Coal Production"),
|
|
("Coffee", "Coffee Production"),
|
|
("Concrete", "Concrete Production"),
|
|
("Corn", "Corn Production"),
|
|
("Cotton", "Cotton Production"),
|
|
("Crystals", "Crystals Production"),
|
|
("Diesel", "Diesel Production"),
|
|
("Dye", "Dye Production"),
|
|
("Electronics", "Electronics Production"),
|
|
("Fertilizer", "Fertilizer Production"),
|
|
("Furniture", "Furniture Production"),
|
|
("Goods", "Goods Production"),
|
|
("Grain", "Grain Production"),
|
|
("Ingots", "Ingots Production"),
|
|
("Iron", "Iron Production"),
|
|
("Livestock", "Livestock Production"),
|
|
("Logs", "Logs Production"),
|
|
("Lumber", "Lumber Production"),
|
|
("Machinery", "Machinery Production"),
|
|
("Mail", "Mail Production"),
|
|
("Meat", "Meat Production"),
|
|
("Medicine", "Medicine Production"),
|
|
("Milk", "Milk Production"),
|
|
("Oil", "Oil Production"),
|
|
("Ore", "Ore Production"),
|
|
("Paper", "Paper Production"),
|
|
("Passengers", "Passengers Production"),
|
|
("Plastic", "Plastic Production"),
|
|
("Produce", "Produce Production"),
|
|
("Pulpwood", "Pulpwood Production"),
|
|
("Rice", "Rice Production"),
|
|
("Rubber", "Rubber Production"),
|
|
("Steel", "Steel Production"),
|
|
("Sugar", "Sugar Production"),
|
|
("Tires", "Tires Production"),
|
|
("Toys", "Toys Production"),
|
|
("Troops", "Troops Production"),
|
|
("Uranium", "Uranium Production"),
|
|
("Waste", "Waste Production"),
|
|
("Weapons", "Weapons Production"),
|
|
("Wool", "Wool Production"),
|
|
];
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct CheckedInCargoBindingsArtifact {
|
|
bindings: Vec<CheckedInCargoBindingRow>,
|
|
}
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
struct CheckedInCargoBindingRow {
|
|
descriptor_id: u32,
|
|
band: String,
|
|
cargo_name: String,
|
|
}
|
|
|
|
fn grounded_named_cargo_price_bindings() -> &'static BTreeMap<u32, (&'static str, &'static str)> {
|
|
static BINDINGS: OnceLock<BTreeMap<u32, (&'static str, &'static str)>> = OnceLock::new();
|
|
BINDINGS.get_or_init(|| {
|
|
let artifact: CheckedInCargoBindingsArtifact = serde_json::from_str(include_str!(
|
|
"../../../artifacts/exports/rt3-1.06/event-effects-cargo-bindings.json"
|
|
))
|
|
.expect("checked-in cargo bindings artifact should parse");
|
|
artifact
|
|
.bindings
|
|
.into_iter()
|
|
.filter(|binding| binding.band == "cargo_price_named")
|
|
.map(|binding| {
|
|
let cargo_name = Box::leak(binding.cargo_name.into_boxed_str()) as &'static str;
|
|
let descriptor_label =
|
|
Box::leak(format!("{cargo_name} Price").into_boxed_str()) as &'static str;
|
|
(binding.descriptor_id, (cargo_name, descriptor_label))
|
|
})
|
|
.collect()
|
|
})
|
|
}
|
|
|
|
pub(crate) fn grounded_named_cargo_price_label(descriptor_id: u32) -> Option<&'static str> {
|
|
grounded_named_cargo_price_bindings()
|
|
.get(&descriptor_id)
|
|
.map(|(cargo_label, _)| *cargo_label)
|
|
}
|
|
|
|
fn grounded_named_cargo_production_label(descriptor_id: u32) -> Option<&'static str> {
|
|
let index = descriptor_id.checked_sub(180)? as usize;
|
|
GROUNDED_NAMED_CARGO_PRODUCTION_LABELS
|
|
.get(index)
|
|
.map(|(cargo_label, _)| *cargo_label)
|
|
}
|
|
|
|
fn grounded_named_cargo_production_descriptor_label(descriptor_id: u32) -> Option<&'static str> {
|
|
let index = descriptor_id.checked_sub(180)? as usize;
|
|
GROUNDED_NAMED_CARGO_PRODUCTION_LABELS
|
|
.get(index)
|
|
.map(|(_, descriptor_label)| *descriptor_label)
|
|
}
|
|
|
|
fn recovered_cargo_production_descriptor_metadata(
|
|
descriptor_id: u32,
|
|
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
|
if let Some(label) = grounded_named_cargo_production_descriptor_label(descriptor_id) {
|
|
return Some(RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id,
|
|
label,
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "cargo_production_scalar",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
});
|
|
}
|
|
recovered_cargo_production_label(descriptor_id).map(|label| {
|
|
RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id,
|
|
label,
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "cargo_production_scalar",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
}
|
|
})
|
|
}
|
|
|
|
fn recovered_cargo_production_slot(descriptor_id: u32) -> Option<u32> {
|
|
let slot = descriptor_id.checked_sub(229)?;
|
|
(1..=11).contains(&slot).then_some(slot)
|
|
}
|
|
|
|
fn recovered_locomotive_availability_descriptor_metadata(
|
|
descriptor_id: u32,
|
|
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
|
if let Some(loco_id) = recovered_locomotive_availability_loco_id(descriptor_id) {
|
|
let label = recovered_locomotive_availability_label(loco_id);
|
|
let executable_in_runtime = (loco_id as usize) <= GROUNDED_LOCOMOTIVE_PREFIX.len();
|
|
return Some(RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id,
|
|
label,
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "locomotive_availability_scalar",
|
|
runtime_key: None,
|
|
runtime_status: if executable_in_runtime {
|
|
RealGroupedEffectRuntimeStatus::Executable
|
|
} else {
|
|
RealGroupedEffectRuntimeStatus::EvidenceBlocked
|
|
},
|
|
executable_in_runtime,
|
|
});
|
|
}
|
|
(457..=474)
|
|
.contains(&descriptor_id)
|
|
.then(|| RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id,
|
|
label: upper_band_locomotive_availability_label(descriptor_id),
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "locomotive_availability_scalar",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::EvidenceBlocked,
|
|
executable_in_runtime: false,
|
|
})
|
|
}
|
|
|
|
fn recovered_locomotive_availability_loco_id(descriptor_id: u32) -> Option<u32> {
|
|
if (241..=351).contains(&descriptor_id) {
|
|
return Some(descriptor_id - 240);
|
|
}
|
|
None
|
|
}
|
|
|
|
fn grounded_locomotive_name(loco_id: u32) -> Option<&'static str> {
|
|
let index = loco_id.checked_sub(1)? as usize;
|
|
GROUNDED_LOCOMOTIVE_PREFIX.get(index).copied()
|
|
}
|
|
|
|
fn recovered_locomotive_availability_label(loco_id: u32) -> &'static str {
|
|
static LABELS: OnceLock<BTreeMap<u32, &'static str>> = OnceLock::new();
|
|
LABELS
|
|
.get_or_init(|| {
|
|
(1..=111)
|
|
.map(|loco_id| {
|
|
let label = grounded_locomotive_name(loco_id)
|
|
.map(|name| format!("{name} Availability"))
|
|
.unwrap_or_else(|| {
|
|
format!("Lower-Band Locomotive Availability Slot {loco_id}")
|
|
});
|
|
(loco_id, Box::leak(label.into_boxed_str()) as &'static str)
|
|
})
|
|
.collect()
|
|
})
|
|
.get(&loco_id)
|
|
.copied()
|
|
.expect("lower-band locomotive availability label should exist")
|
|
}
|
|
|
|
fn upper_band_locomotive_availability_label(descriptor_id: u32) -> &'static str {
|
|
static LABELS: OnceLock<BTreeMap<u32, &'static str>> = OnceLock::new();
|
|
LABELS
|
|
.get_or_init(|| {
|
|
(457..=474)
|
|
.map(|descriptor_id| {
|
|
let label = format!(
|
|
"Upper-Band Locomotive Availability Slot {}",
|
|
descriptor_id - 456
|
|
);
|
|
(
|
|
descriptor_id,
|
|
Box::leak(label.into_boxed_str()) as &'static str,
|
|
)
|
|
})
|
|
.collect()
|
|
})
|
|
.get(&descriptor_id)
|
|
.copied()
|
|
.expect("upper-band locomotive availability label should exist")
|
|
}
|
|
|
|
fn recovered_cargo_production_label(descriptor_id: u32) -> Option<&'static str> {
|
|
known_cargo_slot_definition_for_descriptor_id(descriptor_id).map(|definition| definition.label)
|
|
}
|
|
|
|
fn recovered_cargo_production_slot_from_condition_name(name: &str) -> Option<u32> {
|
|
KNOWN_CARGO_SLOT_DEFINITIONS
|
|
.iter()
|
|
.find(|definition| definition.label == name)
|
|
.map(|definition| definition.slot_id)
|
|
}
|
|
|
|
fn recovered_locomotive_cost_loco_id(descriptor_id: u32) -> Option<u32> {
|
|
if (352..=451).contains(&descriptor_id) {
|
|
return Some(descriptor_id - 351);
|
|
}
|
|
None
|
|
}
|
|
|
|
fn recovered_locomotive_cost_label(descriptor_id: u32) -> Option<&'static str> {
|
|
static LABELS: OnceLock<BTreeMap<u32, &'static str>> = OnceLock::new();
|
|
LABELS
|
|
.get_or_init(|| {
|
|
(352..=451)
|
|
.filter_map(|descriptor_id| {
|
|
recovered_locomotive_cost_loco_id(descriptor_id).map(|loco_id| {
|
|
let label = grounded_locomotive_name(loco_id)
|
|
.map(|name| format!("{name} Cost"))
|
|
.unwrap_or_else(|| {
|
|
format!("Lower-Band Locomotive Cost Slot {loco_id}")
|
|
});
|
|
let label = Box::leak(label.into_boxed_str()) as &'static str;
|
|
(descriptor_id, label)
|
|
})
|
|
})
|
|
.collect()
|
|
})
|
|
.get(&descriptor_id)
|
|
.copied()
|
|
.or_else(|| upper_band_locomotive_cost_label(descriptor_id))
|
|
}
|
|
|
|
fn upper_band_locomotive_cost_label(descriptor_id: u32) -> Option<&'static str> {
|
|
static LABELS: OnceLock<BTreeMap<u32, &'static str>> = OnceLock::new();
|
|
LABELS
|
|
.get_or_init(|| {
|
|
(475..=502)
|
|
.map(|descriptor_id| {
|
|
let label = format!("Upper-Band Locomotive Cost Slot {}", descriptor_id - 474);
|
|
(
|
|
descriptor_id,
|
|
Box::leak(label.into_boxed_str()) as &'static str,
|
|
)
|
|
})
|
|
.collect()
|
|
})
|
|
.get(&descriptor_id)
|
|
.copied()
|
|
}
|
|
|
|
fn recovered_locomotive_cost_descriptor_metadata(
|
|
descriptor_id: u32,
|
|
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
|
recovered_locomotive_cost_label(descriptor_id).map(|label| {
|
|
let executable_in_runtime = recovered_locomotive_cost_loco_id(descriptor_id)
|
|
.is_some_and(|loco_id| (loco_id as usize) <= GROUNDED_LOCOMOTIVE_PREFIX.len());
|
|
RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id,
|
|
label,
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "locomotive_cost_scalar",
|
|
runtime_key: None,
|
|
runtime_status: if executable_in_runtime {
|
|
RealGroupedEffectRuntimeStatus::Executable
|
|
} else {
|
|
RealGroupedEffectRuntimeStatus::EvidenceBlocked
|
|
},
|
|
executable_in_runtime,
|
|
}
|
|
})
|
|
}
|
|
|
|
fn recovered_territory_access_cost_descriptor_metadata(
|
|
descriptor_id: u32,
|
|
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
|
(descriptor_id == 453).then_some(RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id,
|
|
label: "Territory Access Cost",
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "territory_access_cost_scalar",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
})
|
|
}
|
|
|
|
fn recovered_locomotive_policy_descriptor_metadata(
|
|
descriptor_id: u32,
|
|
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
|
match descriptor_id {
|
|
454 => Some(RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id,
|
|
label: "All Steam Locos Avail.",
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "world_flag_toggle",
|
|
runtime_key: Some("world.all_steam_locos_available"),
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
}),
|
|
455 => Some(RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id,
|
|
label: "All Diesel Locos Avail.",
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "world_flag_toggle",
|
|
runtime_key: Some("world.all_diesel_locos_available"),
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
}),
|
|
456 => Some(RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id,
|
|
label: "All Electric Locos Avail.",
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "world_flag_toggle",
|
|
runtime_key: Some("world.all_electric_locos_available"),
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
}),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn special_condition_world_scalar_descriptor_metadata(
|
|
descriptor_id: u32,
|
|
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
|
let slot_index = descriptor_id.checked_sub(110)? as usize;
|
|
if slot_index != 12 {
|
|
return None;
|
|
}
|
|
let definition = KNOWN_SPECIAL_CONDITION_DEFINITIONS.get(slot_index)?;
|
|
if definition.hidden {
|
|
return None;
|
|
}
|
|
Some(RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id,
|
|
label: definition.label,
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "world_track_build_limit_scalar",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
})
|
|
}
|
|
|
|
fn special_condition_world_toggle_descriptor_metadata(
|
|
descriptor_id: u32,
|
|
) -> Option<RealGroupedEffectDescriptorMetadata> {
|
|
let slot_index = descriptor_id.checked_sub(110)? as usize;
|
|
if !(1..=34).contains(&slot_index) || matches!(slot_index, 12 | 31) {
|
|
return None;
|
|
}
|
|
let definition = KNOWN_SPECIAL_CONDITION_DEFINITIONS.get(slot_index)?;
|
|
if definition.hidden {
|
|
return None;
|
|
}
|
|
Some(RealGroupedEffectDescriptorMetadata {
|
|
descriptor_id,
|
|
label: definition.label,
|
|
target_mask_bits: 0x08,
|
|
parameter_family: "world_flag_toggle",
|
|
runtime_key: None,
|
|
runtime_status: RealGroupedEffectRuntimeStatus::Executable,
|
|
executable_in_runtime: true,
|
|
})
|
|
}
|
|
|
|
fn classify_real_grouped_effect_semantic_family(
|
|
opcode: u8,
|
|
raw_scalar_value: i32,
|
|
value_byte_0x11: u8,
|
|
value_byte_0x12: u8,
|
|
value_word_0x14: u16,
|
|
value_word_0x16: u16,
|
|
) -> &'static str {
|
|
if opcode == 8 {
|
|
return "multivalue_scalar";
|
|
}
|
|
if value_byte_0x11 != 0 || value_byte_0x12 != 0 || value_word_0x14 != 0 || value_word_0x16 != 0
|
|
{
|
|
return "timed_duration";
|
|
}
|
|
if raw_scalar_value == 0 || raw_scalar_value == 1 {
|
|
return "bool_toggle";
|
|
}
|
|
"scalar_assignment"
|
|
}
|
|
|
|
fn classify_real_grouped_effect_row_shape(
|
|
opcode: u8,
|
|
raw_scalar_value: i32,
|
|
value_byte_0x11: u8,
|
|
value_byte_0x12: u8,
|
|
value_word_0x14: u16,
|
|
value_word_0x16: u16,
|
|
) -> &'static str {
|
|
if opcode == 8 {
|
|
return "multivalue_scalar";
|
|
}
|
|
if value_byte_0x11 != 0 || value_byte_0x12 != 0 || value_word_0x14 != 0 || value_word_0x16 != 0
|
|
{
|
|
return "timed_duration";
|
|
}
|
|
if raw_scalar_value == 0 || raw_scalar_value == 1 {
|
|
return "bool_toggle";
|
|
}
|
|
"scalar_assignment"
|
|
}
|
|
|
|
fn build_real_grouped_effect_semantic_preview(
|
|
descriptor_label: Option<&str>,
|
|
semantic_family: &str,
|
|
raw_scalar_value: i32,
|
|
value_byte_0x11: u8,
|
|
value_byte_0x12: u8,
|
|
value_word_0x14: u16,
|
|
value_word_0x16: u16,
|
|
) -> String {
|
|
let label = descriptor_label.unwrap_or("descriptor");
|
|
match semantic_family {
|
|
"bool_toggle" => {
|
|
let state = if raw_scalar_value == 0 {
|
|
"FALSE"
|
|
} else {
|
|
"TRUE"
|
|
};
|
|
format!("Set {label} to {state}")
|
|
}
|
|
"building_spawn_batch" => format!(
|
|
"Batch place {label} with scalar {raw_scalar_value}, count {value_byte_0x11}, and span words [{value_word_0x14}, {value_word_0x16}]"
|
|
),
|
|
"timed_duration" => format!(
|
|
"Set {label} to {raw_scalar_value} for {value_word_0x14} years {value_word_0x16} months"
|
|
),
|
|
"multivalue_scalar" => format!(
|
|
"Set {label} to {raw_scalar_value} with aux [{value_byte_0x11}, {value_byte_0x12}, {value_word_0x14}, {value_word_0x16}]"
|
|
),
|
|
_ => format!("Set {label} to {raw_scalar_value}"),
|
|
}
|
|
}
|
|
|
|
fn runtime_candidate_availability_name(label: &str) -> String {
|
|
label
|
|
.strip_suffix(" Availability")
|
|
.unwrap_or(label)
|
|
.to_string()
|
|
}
|
|
|
|
fn runtime_world_flag_key(
|
|
descriptor_metadata: RealGroupedEffectDescriptorMetadata,
|
|
) -> Option<String> {
|
|
descriptor_metadata
|
|
.runtime_key
|
|
.map(str::to_string)
|
|
.or_else(|| {
|
|
(descriptor_metadata.parameter_family == "world_flag_toggle")
|
|
.then(|| runtime_world_flag_key_from_label(descriptor_metadata.label))
|
|
})
|
|
}
|
|
|
|
pub(crate) fn runtime_world_flag_key_from_label(label: &str) -> String {
|
|
normalize_runtime_world_key(label)
|
|
}
|
|
|
|
fn runtime_world_scalar_key(
|
|
descriptor_metadata: RealGroupedEffectDescriptorMetadata,
|
|
) -> Option<String> {
|
|
descriptor_metadata
|
|
.runtime_key
|
|
.map(str::to_string)
|
|
.or_else(|| {
|
|
(descriptor_metadata.parameter_family == "world_scalar_override")
|
|
.then(|| normalize_runtime_world_key(descriptor_metadata.label))
|
|
})
|
|
}
|
|
|
|
pub(crate) fn runtime_world_scalar_key_from_label(label: &str) -> String {
|
|
normalize_runtime_world_key(label)
|
|
}
|
|
|
|
fn normalize_runtime_world_key(label: &str) -> String {
|
|
let mut key = String::with_capacity(label.len() + 6);
|
|
key.push_str("world.");
|
|
let mut last_was_underscore = false;
|
|
for ch in label.chars() {
|
|
if ch.is_ascii_alphanumeric() {
|
|
key.push(ch.to_ascii_lowercase());
|
|
last_was_underscore = false;
|
|
} else if !last_was_underscore {
|
|
key.push('_');
|
|
last_was_underscore = true;
|
|
}
|
|
}
|
|
while key.ends_with('_') {
|
|
key.pop();
|
|
}
|
|
key
|
|
}
|
|
|
|
fn real_grouped_company_governance_metric(
|
|
descriptor_metadata: RealGroupedEffectDescriptorMetadata,
|
|
) -> Option<RuntimeCompanyMetric> {
|
|
match descriptor_metadata.label {
|
|
"Credit Rating" => Some(RuntimeCompanyMetric::CreditRating),
|
|
"Prime Rate" => Some(RuntimeCompanyMetric::PrimeRate),
|
|
"Book Value Per Share" => Some(RuntimeCompanyMetric::BookValuePerShare),
|
|
"Investor Confidence" => Some(RuntimeCompanyMetric::InvestorConfidence),
|
|
"Management Attitude" => Some(RuntimeCompanyMetric::ManagementAttitude),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn derive_real_grouped_target_subject(
|
|
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
|
|
compact_control: &SmpLoadedPackedEventCompactControlSummary,
|
|
) -> Option<RealGroupedTargetSubject> {
|
|
if row.parameter_family.as_deref() == Some("company_governance_scalar") {
|
|
return Some(RealGroupedTargetSubject::Company);
|
|
}
|
|
if row.parameter_family.as_deref() == Some("world_scalar_override") {
|
|
return Some(RealGroupedTargetSubject::WholeGame);
|
|
}
|
|
match row.target_mask_bits {
|
|
Some(0x08) => Some(RealGroupedTargetSubject::WholeGame),
|
|
Some(0x01) => Some(RealGroupedTargetSubject::Company),
|
|
Some(0x04) => Some(RealGroupedTargetSubject::Territory),
|
|
Some(0x02) => match compact_control
|
|
.grouped_scope_checkboxes_0x7ff
|
|
.get(row.group_index)
|
|
.copied()
|
|
{
|
|
Some(2) => Some(RealGroupedTargetSubject::Chairman),
|
|
_ => Some(RealGroupedTargetSubject::Player),
|
|
},
|
|
_ if row.descriptor_id == 3 => Some(RealGroupedTargetSubject::Territory),
|
|
_ if row.descriptor_id == 15
|
|
&& compact_control
|
|
.grouped_territory_selectors_0x80f
|
|
.get(row.group_index)
|
|
.is_some_and(|selector| *selector >= 0) =>
|
|
{
|
|
Some(RealGroupedTargetSubject::Territory)
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn real_grouped_target_subject_name(subject: RealGroupedTargetSubject) -> &'static str {
|
|
match subject {
|
|
RealGroupedTargetSubject::Company => "company",
|
|
RealGroupedTargetSubject::Player => "player",
|
|
RealGroupedTargetSubject::Chairman => "chairman",
|
|
RealGroupedTargetSubject::Territory => "territory",
|
|
RealGroupedTargetSubject::WholeGame => "whole_game",
|
|
}
|
|
}
|
|
|
|
fn derive_real_grouped_target_scope_name(
|
|
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
|
|
compact_control: &SmpLoadedPackedEventCompactControlSummary,
|
|
target_subject: Option<RealGroupedTargetSubject>,
|
|
target_scope_ordinal: Option<u8>,
|
|
) -> Option<String> {
|
|
match target_subject {
|
|
Some(RealGroupedTargetSubject::Company) => target_scope_ordinal
|
|
.map(real_grouped_company_scope_name)
|
|
.map(str::to_string),
|
|
Some(RealGroupedTargetSubject::Player) => target_scope_ordinal
|
|
.map(real_grouped_player_scope_name)
|
|
.map(str::to_string),
|
|
Some(RealGroupedTargetSubject::Chairman) => target_scope_ordinal
|
|
.map(real_grouped_chairman_scope_name)
|
|
.map(str::to_string),
|
|
Some(RealGroupedTargetSubject::Territory) => compact_control
|
|
.grouped_territory_selectors_0x80f
|
|
.get(row.group_index)
|
|
.copied()
|
|
.filter(|selector| *selector >= 0)
|
|
.map(|_| "specified_territories".to_string()),
|
|
Some(RealGroupedTargetSubject::WholeGame) => Some("whole_game".to_string()),
|
|
None => None,
|
|
}
|
|
}
|
|
|
|
fn real_grouped_company_scope_name(ordinal: u8) -> &'static str {
|
|
match ordinal {
|
|
0 => "condition_true_company",
|
|
1 => "selected_company",
|
|
2 => "human_companies",
|
|
3 => "ai_companies",
|
|
_ => "unsupported_company_scope",
|
|
}
|
|
}
|
|
|
|
fn real_grouped_player_scope_name(ordinal: u8) -> &'static str {
|
|
match ordinal {
|
|
0 => "condition_true_player",
|
|
1 => "selected_player",
|
|
2 => "human_players",
|
|
3 => "ai_players",
|
|
_ => "unsupported_player_scope",
|
|
}
|
|
}
|
|
|
|
fn real_grouped_chairman_scope_name(ordinal: u8) -> &'static str {
|
|
match ordinal {
|
|
0 => "condition_true_chairman",
|
|
1 => "selected_chairman",
|
|
2 => "human_chairmen",
|
|
3 => "ai_chairmen",
|
|
_ => "unsupported_chairman_scope",
|
|
}
|
|
}
|
|
|
|
fn runtime_variable_index(descriptor_id: u32) -> Option<u32> {
|
|
match descriptor_id {
|
|
39..=42 => Some(descriptor_id - 38),
|
|
43..=46 => Some(descriptor_id - 42),
|
|
47..=50 => Some(descriptor_id - 46),
|
|
51..=54 => Some(descriptor_id - 50),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn decode_real_grouped_effect_actions(
|
|
grouped_effect_rows: &[SmpLoadedPackedEventGroupedEffectRowSummary],
|
|
compact_control: &SmpLoadedPackedEventCompactControlSummary,
|
|
) -> Vec<RuntimeEffect> {
|
|
grouped_effect_rows
|
|
.iter()
|
|
.filter_map(|row| decode_real_grouped_effect_action(row, compact_control))
|
|
.collect()
|
|
}
|
|
|
|
fn decode_real_grouped_effect_action(
|
|
row: &SmpLoadedPackedEventGroupedEffectRowSummary,
|
|
compact_control: &SmpLoadedPackedEventCompactControlSummary,
|
|
) -> Option<RuntimeEffect> {
|
|
let descriptor_metadata = real_grouped_effect_descriptor_metadata(row.descriptor_id)?;
|
|
let target_scope_ordinal = compact_control
|
|
.grouped_target_scope_ordinals_0x7fb
|
|
.get(row.group_index)
|
|
.copied()?;
|
|
let target_subject = derive_real_grouped_target_subject(row, compact_control);
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.parameter_family == "runtime_variable_scalar"
|
|
&& row.row_shape == "scalar_assignment"
|
|
{
|
|
let index = runtime_variable_index(descriptor_metadata.descriptor_id)?;
|
|
return match target_subject {
|
|
Some(RealGroupedTargetSubject::WholeGame) => Some(RuntimeEffect::SetWorldVariable {
|
|
index,
|
|
value: i64::from(row.raw_scalar_value),
|
|
}),
|
|
Some(RealGroupedTargetSubject::Company) => Some(RuntimeEffect::SetCompanyVariable {
|
|
target: real_grouped_company_target(target_scope_ordinal)?,
|
|
index,
|
|
value: i64::from(row.raw_scalar_value),
|
|
}),
|
|
Some(RealGroupedTargetSubject::Player) => Some(RuntimeEffect::SetPlayerVariable {
|
|
target: real_grouped_player_target(target_scope_ordinal)?,
|
|
index,
|
|
value: i64::from(row.raw_scalar_value),
|
|
}),
|
|
Some(RealGroupedTargetSubject::Territory) => compact_control
|
|
.grouped_territory_selectors_0x80f
|
|
.get(row.group_index)
|
|
.copied()
|
|
.filter(|selector| *selector >= 0)
|
|
.map(|selector| RuntimeEffect::SetTerritoryVariable {
|
|
target: RuntimeTerritoryTarget::Ids {
|
|
ids: vec![selector as u32],
|
|
},
|
|
index,
|
|
value: i64::from(row.raw_scalar_value),
|
|
}),
|
|
_ => None,
|
|
};
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.parameter_family == "company_governance_scalar"
|
|
&& row.row_shape == "scalar_assignment"
|
|
{
|
|
let target = real_grouped_company_target(target_scope_ordinal)?;
|
|
let metric = real_grouped_company_governance_metric(descriptor_metadata)?;
|
|
return Some(RuntimeEffect::SetCompanyGovernanceScalar {
|
|
target,
|
|
metric,
|
|
value: i64::from(row.raw_scalar_value),
|
|
});
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.descriptor_id == 1
|
|
&& row.opcode == 8
|
|
&& row.row_shape == "multivalue_scalar"
|
|
{
|
|
return match target_subject {
|
|
Some(RealGroupedTargetSubject::Chairman) => Some(RuntimeEffect::SetChairmanCash {
|
|
target: real_grouped_chairman_target(target_scope_ordinal)?,
|
|
value: i64::from(row.raw_scalar_value),
|
|
}),
|
|
_ => Some(RuntimeEffect::SetPlayerCash {
|
|
target: real_grouped_player_target(target_scope_ordinal)?,
|
|
value: i64::from(row.raw_scalar_value),
|
|
}),
|
|
};
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.descriptor_id == 2
|
|
&& row.opcode == 8
|
|
&& row.row_shape == "multivalue_scalar"
|
|
{
|
|
let target = real_grouped_company_target(target_scope_ordinal)?;
|
|
return Some(RuntimeEffect::SetCompanyCash {
|
|
target,
|
|
value: i64::from(row.raw_scalar_value),
|
|
});
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.descriptor_id == 3
|
|
&& row.row_shape == "bool_toggle"
|
|
&& row.raw_scalar_value != 0
|
|
{
|
|
let target = real_grouped_company_target(target_scope_ordinal)?;
|
|
let territory = compact_control
|
|
.grouped_territory_selectors_0x80f
|
|
.get(row.group_index)
|
|
.copied()
|
|
.filter(|selector| *selector >= 0)
|
|
.map(|selector| RuntimeTerritoryTarget::Ids {
|
|
ids: vec![selector as u32],
|
|
})?;
|
|
return Some(RuntimeEffect::SetCompanyTerritoryAccess {
|
|
target,
|
|
territory,
|
|
value: true,
|
|
});
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.descriptor_id == 8
|
|
&& row.row_shape == "scalar_assignment"
|
|
{
|
|
return Some(RuntimeEffect::SetEconomicStatusCode {
|
|
value: row.raw_scalar_value,
|
|
});
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.descriptor_id == 108
|
|
&& row.row_shape == "scalar_assignment"
|
|
{
|
|
return Some(RuntimeEffect::SetSpecialCondition {
|
|
label: descriptor_metadata.label.to_string(),
|
|
value: row.raw_scalar_value as u32,
|
|
});
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.descriptor_id == 109
|
|
&& row.row_shape == "scalar_assignment"
|
|
{
|
|
return Some(RuntimeEffect::SetCandidateAvailability {
|
|
name: runtime_candidate_availability_name(descriptor_metadata.label),
|
|
value: row.raw_scalar_value as u32,
|
|
});
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.descriptor_id == 122
|
|
&& row.row_shape == "scalar_assignment"
|
|
{
|
|
return Some(RuntimeEffect::SetLimitedTrackBuildingAmount {
|
|
value: row.raw_scalar_value,
|
|
});
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.parameter_family == "world_scalar_override"
|
|
&& row.row_shape == "scalar_assignment"
|
|
{
|
|
return Some(RuntimeEffect::SetWorldScalarOverride {
|
|
key: runtime_world_scalar_key(descriptor_metadata)?,
|
|
value: i64::from(row.raw_scalar_value),
|
|
});
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.parameter_family == "cargo_price_scalar"
|
|
&& row.row_shape == "scalar_assignment"
|
|
&& row.raw_scalar_value >= 0
|
|
{
|
|
return match row.descriptor_id {
|
|
105 => Some(RuntimeEffect::SetCargoPriceOverride {
|
|
target: RuntimeCargoPriceTarget::All,
|
|
value: row.raw_scalar_value as u32,
|
|
}),
|
|
descriptor_id => grounded_named_cargo_price_label(descriptor_id).map(|name| {
|
|
RuntimeEffect::SetCargoPriceOverride {
|
|
target: RuntimeCargoPriceTarget::Named {
|
|
name: name.to_string(),
|
|
},
|
|
value: row.raw_scalar_value as u32,
|
|
}
|
|
}),
|
|
};
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.parameter_family == "cargo_production_scalar"
|
|
&& row.row_shape == "scalar_assignment"
|
|
&& row.raw_scalar_value >= 0
|
|
{
|
|
return match descriptor_metadata.descriptor_id {
|
|
177 => Some(RuntimeEffect::SetCargoProductionOverride {
|
|
target: RuntimeCargoProductionTarget::All,
|
|
value: row.raw_scalar_value as u32,
|
|
}),
|
|
178 => Some(RuntimeEffect::SetCargoProductionOverride {
|
|
target: RuntimeCargoProductionTarget::Factory,
|
|
value: row.raw_scalar_value as u32,
|
|
}),
|
|
179 => Some(RuntimeEffect::SetCargoProductionOverride {
|
|
target: RuntimeCargoProductionTarget::FarmMine,
|
|
value: row.raw_scalar_value as u32,
|
|
}),
|
|
230..=240 => {
|
|
let slot = descriptor_metadata.descriptor_id.checked_sub(229)?;
|
|
Some(RuntimeEffect::SetCargoProductionSlot {
|
|
slot,
|
|
value: row.raw_scalar_value as u32,
|
|
})
|
|
}
|
|
_ => None,
|
|
};
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.parameter_family == "territory_access_cost_scalar"
|
|
&& row.row_shape == "scalar_assignment"
|
|
&& row.raw_scalar_value >= 0
|
|
{
|
|
return Some(RuntimeEffect::SetTerritoryAccessCost {
|
|
value: row.raw_scalar_value as u32,
|
|
});
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.parameter_family == "world_flag_toggle"
|
|
&& row.row_shape == "bool_toggle"
|
|
{
|
|
return Some(RuntimeEffect::SetWorldFlag {
|
|
key: runtime_world_flag_key(descriptor_metadata)?,
|
|
value: row.raw_scalar_value != 0,
|
|
});
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.descriptor_id == 9
|
|
&& row.row_shape == "bool_toggle"
|
|
&& row.raw_scalar_value != 0
|
|
{
|
|
let target = real_grouped_company_target(target_scope_ordinal)?;
|
|
return Some(RuntimeEffect::ConfiscateCompanyAssets { target });
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.descriptor_id == 13
|
|
&& row.row_shape == "bool_toggle"
|
|
&& row.raw_scalar_value != 0
|
|
{
|
|
let target = real_grouped_company_target(target_scope_ordinal)?;
|
|
return Some(RuntimeEffect::DeactivateCompany { target });
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.descriptor_id == 14
|
|
&& row.row_shape == "bool_toggle"
|
|
&& row.raw_scalar_value != 0
|
|
{
|
|
return match target_subject {
|
|
Some(RealGroupedTargetSubject::Chairman) => Some(RuntimeEffect::DeactivateChairman {
|
|
target: real_grouped_chairman_target(target_scope_ordinal)?,
|
|
}),
|
|
_ => Some(RuntimeEffect::DeactivatePlayer {
|
|
target: real_grouped_player_target(target_scope_ordinal)?,
|
|
}),
|
|
};
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.descriptor_id == 16
|
|
&& row.row_shape == "scalar_assignment"
|
|
&& row.raw_scalar_value >= 0
|
|
{
|
|
let target = real_grouped_company_target(target_scope_ordinal)?;
|
|
return Some(RuntimeEffect::SetCompanyTrackLayingCapacity {
|
|
target,
|
|
value: Some(row.raw_scalar_value as u32),
|
|
});
|
|
}
|
|
|
|
if descriptor_metadata.executable_in_runtime
|
|
&& descriptor_metadata.descriptor_id == 15
|
|
&& row.row_shape == "bool_toggle"
|
|
&& row.raw_scalar_value != 0
|
|
{
|
|
let company_target = real_grouped_company_target(target_scope_ordinal);
|
|
let territory_target = compact_control
|
|
.grouped_territory_selectors_0x80f
|
|
.get(row.group_index)
|
|
.copied()
|
|
.filter(|selector| *selector >= 0)
|
|
.map(|selector| RuntimeTerritoryTarget::Ids {
|
|
ids: vec![selector as u32],
|
|
});
|
|
if company_target.is_none() && territory_target.is_none() {
|
|
return None;
|
|
}
|
|
return Some(RuntimeEffect::RetireTrains {
|
|
company_target,
|
|
territory_target,
|
|
locomotive_name: row.locomotive_name.clone(),
|
|
});
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn real_grouped_company_target(ordinal: u8) -> Option<RuntimeCompanyTarget> {
|
|
match ordinal {
|
|
0 => Some(RuntimeCompanyTarget::ConditionTrueCompany),
|
|
1 => Some(RuntimeCompanyTarget::SelectedCompany),
|
|
2 => Some(RuntimeCompanyTarget::HumanCompanies),
|
|
3 => Some(RuntimeCompanyTarget::AiCompanies),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn real_grouped_player_target(ordinal: u8) -> Option<RuntimePlayerTarget> {
|
|
match ordinal {
|
|
0 => Some(RuntimePlayerTarget::ConditionTruePlayer),
|
|
1 => Some(RuntimePlayerTarget::SelectedPlayer),
|
|
2 => Some(RuntimePlayerTarget::HumanPlayers),
|
|
3 => Some(RuntimePlayerTarget::AiPlayers),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn real_grouped_chairman_target(ordinal: u8) -> Option<RuntimeChairmanTarget> {
|
|
match ordinal {
|
|
0 => Some(RuntimeChairmanTarget::ConditionTrueChairman),
|
|
1 => Some(RuntimeChairmanTarget::SelectedChairman),
|
|
2 => Some(RuntimeChairmanTarget::HumanChairmen),
|
|
3 => Some(RuntimeChairmanTarget::AiChairmen),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn real_grouped_chairman_target_supported_in_runtime(ordinal: u8) -> bool {
|
|
real_grouped_chairman_target(ordinal).is_some()
|
|
}
|
|
|
|
fn parse_synthetic_packed_event_action(bytes: &[u8], cursor: &mut usize) -> Option<RuntimeEffect> {
|
|
let opcode = read_u8_at(bytes, *cursor)?;
|
|
*cursor += 1;
|
|
match opcode {
|
|
0x01 => {
|
|
let key = parse_len_prefixed_string(bytes, cursor)?;
|
|
let value = read_u8_at(bytes, *cursor)? != 0;
|
|
*cursor += 1;
|
|
Some(RuntimeEffect::SetWorldFlag { key, value })
|
|
}
|
|
0x02 => {
|
|
let target = parse_synthetic_company_target(bytes, cursor)?;
|
|
let delta = read_i64_at(bytes, *cursor)?;
|
|
*cursor += 8;
|
|
Some(RuntimeEffect::AdjustCompanyCash { target, delta })
|
|
}
|
|
0x03 => {
|
|
let target = parse_synthetic_company_target(bytes, cursor)?;
|
|
let delta = read_i64_at(bytes, *cursor)?;
|
|
*cursor += 8;
|
|
Some(RuntimeEffect::AdjustCompanyDebt { target, delta })
|
|
}
|
|
0x04 => {
|
|
let name = parse_len_prefixed_string(bytes, cursor)?;
|
|
let value = read_u32_at(bytes, *cursor)?;
|
|
*cursor += 4;
|
|
Some(RuntimeEffect::SetCandidateAvailability { name, value })
|
|
}
|
|
0x05 => {
|
|
let label = parse_len_prefixed_string(bytes, cursor)?;
|
|
let value = read_u32_at(bytes, *cursor)?;
|
|
*cursor += 4;
|
|
Some(RuntimeEffect::SetSpecialCondition { label, value })
|
|
}
|
|
0x06 => {
|
|
let template_len = usize::try_from(read_u32_at(bytes, *cursor)?).ok()?;
|
|
*cursor += 4;
|
|
let template_bytes = bytes.get(*cursor..*cursor + template_len)?;
|
|
let record = parse_synthetic_event_runtime_record_template(template_bytes)?;
|
|
*cursor += template_len;
|
|
Some(RuntimeEffect::AppendEventRecord {
|
|
record: Box::new(record),
|
|
})
|
|
}
|
|
0x07 => {
|
|
let record_id = read_u32_at(bytes, *cursor)?;
|
|
*cursor += 4;
|
|
Some(RuntimeEffect::ActivateEventRecord { record_id })
|
|
}
|
|
0x08 => {
|
|
let record_id = read_u32_at(bytes, *cursor)?;
|
|
*cursor += 4;
|
|
Some(RuntimeEffect::DeactivateEventRecord { record_id })
|
|
}
|
|
0x09 => {
|
|
let record_id = read_u32_at(bytes, *cursor)?;
|
|
*cursor += 4;
|
|
Some(RuntimeEffect::RemoveEventRecord { record_id })
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn parse_synthetic_event_runtime_record_template(
|
|
bytes: &[u8],
|
|
) -> Option<RuntimeEventRecordTemplate> {
|
|
if !bytes.starts_with(PACKED_EVENT_RECORD_TEMPLATE_SYNTHETIC_MAGIC) {
|
|
return None;
|
|
}
|
|
|
|
let mut cursor = PACKED_EVENT_RECORD_TEMPLATE_SYNTHETIC_MAGIC.len();
|
|
let record_id = read_u32_at(bytes, cursor)?;
|
|
cursor += 4;
|
|
let trigger_kind = read_u8_at(bytes, cursor)?;
|
|
cursor += 1;
|
|
let flags = read_u8_at(bytes, cursor)?;
|
|
cursor += 1;
|
|
let action_count = usize::from(read_u8_at(bytes, cursor)?);
|
|
cursor += 1;
|
|
cursor += 1;
|
|
|
|
let mut effects = Vec::with_capacity(action_count);
|
|
for _ in 0..action_count {
|
|
effects.push(parse_synthetic_packed_event_action(bytes, &mut cursor)?);
|
|
}
|
|
|
|
if cursor != bytes.len() {
|
|
return None;
|
|
}
|
|
|
|
Some(RuntimeEventRecordTemplate {
|
|
record_id,
|
|
trigger_kind,
|
|
active: flags & 0x01 != 0,
|
|
marks_collection_dirty: flags & 0x02 != 0,
|
|
one_shot: flags & 0x04 != 0,
|
|
conditions: Vec::new(),
|
|
effects,
|
|
})
|
|
}
|
|
|
|
fn parse_synthetic_company_target(
|
|
bytes: &[u8],
|
|
cursor: &mut usize,
|
|
) -> Option<RuntimeCompanyTarget> {
|
|
let target_kind = read_u8_at(bytes, *cursor)?;
|
|
*cursor += 1;
|
|
match target_kind {
|
|
0x00 => Some(RuntimeCompanyTarget::AllActive),
|
|
0x01 => {
|
|
let count = usize::from(read_u8_at(bytes, *cursor)?);
|
|
*cursor += 1;
|
|
let mut ids = Vec::with_capacity(count);
|
|
for _ in 0..count {
|
|
ids.push(read_u32_at(bytes, *cursor)?);
|
|
*cursor += 4;
|
|
}
|
|
Some(RuntimeCompanyTarget::Ids { ids })
|
|
}
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn parse_len_prefixed_string(bytes: &[u8], cursor: &mut usize) -> Option<String> {
|
|
let len = usize::from(read_u8_at(bytes, *cursor)?);
|
|
*cursor += 1;
|
|
let text_bytes = bytes.get(*cursor..*cursor + len)?;
|
|
*cursor += len;
|
|
Some(String::from_utf8_lossy(text_bytes).into_owned())
|
|
}
|
|
|
|
fn parse_optional_u16_len_prefixed_string(
|
|
bytes: &[u8],
|
|
cursor: &mut usize,
|
|
) -> Option<Option<String>> {
|
|
let len = usize::from(read_u16_at(bytes, *cursor)?);
|
|
*cursor += 2;
|
|
if len == 0 {
|
|
return Some(None);
|
|
}
|
|
let text_bytes = bytes.get(*cursor..*cursor + len)?;
|
|
*cursor += len;
|
|
Some(Some(String::from_utf8_lossy(text_bytes).into_owned()))
|
|
}
|
|
|
|
fn runtime_effect_supported_for_save_import(effect: &RuntimeEffect) -> bool {
|
|
match effect {
|
|
RuntimeEffect::SetChairmanCash { target, .. }
|
|
| RuntimeEffect::DeactivateChairman { target } => matches!(
|
|
target,
|
|
RuntimeChairmanTarget::AllActive
|
|
| RuntimeChairmanTarget::HumanChairmen
|
|
| RuntimeChairmanTarget::AiChairmen
|
|
| RuntimeChairmanTarget::SelectedChairman
|
|
| RuntimeChairmanTarget::ConditionTrueChairman
|
|
| RuntimeChairmanTarget::Ids { .. }
|
|
),
|
|
RuntimeEffect::SetWorldFlag { .. }
|
|
| RuntimeEffect::SetWorldVariable { .. }
|
|
| RuntimeEffect::SetLimitedTrackBuildingAmount { .. }
|
|
| RuntimeEffect::SetEconomicStatusCode { .. }
|
|
| RuntimeEffect::SetCompanyGovernanceScalar { .. }
|
|
| RuntimeEffect::SetCandidateAvailability { .. }
|
|
| RuntimeEffect::SetNamedLocomotiveAvailability { .. }
|
|
| RuntimeEffect::SetNamedLocomotiveAvailabilityValue { .. }
|
|
| RuntimeEffect::SetNamedLocomotiveCost { .. }
|
|
| RuntimeEffect::SetCargoPriceOverride { .. }
|
|
| RuntimeEffect::SetCargoProductionOverride { .. }
|
|
| RuntimeEffect::SetCargoProductionSlot { .. }
|
|
| RuntimeEffect::SetWorldScalarOverride { .. }
|
|
| RuntimeEffect::SetTerritoryAccessCost { .. }
|
|
| RuntimeEffect::SetSpecialCondition { .. }
|
|
| RuntimeEffect::ConfiscateCompanyAssets { .. }
|
|
| RuntimeEffect::DeactivateCompany { .. }
|
|
| RuntimeEffect::DeactivatePlayer { .. }
|
|
| RuntimeEffect::SetCompanyTrackLayingCapacity { .. }
|
|
| RuntimeEffect::RetireTrains { .. }
|
|
| RuntimeEffect::ActivateEventRecord { .. }
|
|
| RuntimeEffect::DeactivateEventRecord { .. }
|
|
| RuntimeEffect::RemoveEventRecord { .. } => true,
|
|
RuntimeEffect::SetPlayerCash { target, .. }
|
|
| RuntimeEffect::SetPlayerVariable { target, .. } => matches!(
|
|
target,
|
|
RuntimePlayerTarget::AllActive
|
|
| RuntimePlayerTarget::Ids { .. }
|
|
| RuntimePlayerTarget::HumanPlayers
|
|
| RuntimePlayerTarget::AiPlayers
|
|
| RuntimePlayerTarget::SelectedPlayer
|
|
| RuntimePlayerTarget::ConditionTruePlayer
|
|
),
|
|
RuntimeEffect::SetCompanyTerritoryAccess {
|
|
target, territory, ..
|
|
} => {
|
|
matches!(
|
|
target,
|
|
RuntimeCompanyTarget::AllActive
|
|
| RuntimeCompanyTarget::Ids { .. }
|
|
| RuntimeCompanyTarget::HumanCompanies
|
|
| RuntimeCompanyTarget::AiCompanies
|
|
| RuntimeCompanyTarget::SelectedCompany
|
|
| RuntimeCompanyTarget::ConditionTrueCompany
|
|
) && matches!(
|
|
territory,
|
|
RuntimeTerritoryTarget::AllTerritories | RuntimeTerritoryTarget::Ids { .. }
|
|
)
|
|
}
|
|
RuntimeEffect::SetCompanyCash { target, .. }
|
|
| RuntimeEffect::SetCompanyVariable { target, .. }
|
|
| RuntimeEffect::AdjustCompanyCash { target, .. }
|
|
| RuntimeEffect::AdjustCompanyDebt { target, .. } => matches!(
|
|
target,
|
|
RuntimeCompanyTarget::AllActive
|
|
| RuntimeCompanyTarget::Ids { .. }
|
|
| RuntimeCompanyTarget::HumanCompanies
|
|
| RuntimeCompanyTarget::AiCompanies
|
|
| RuntimeCompanyTarget::SelectedCompany
|
|
| RuntimeCompanyTarget::ConditionTrueCompany
|
|
),
|
|
RuntimeEffect::SetTerritoryVariable { target, .. } => matches!(
|
|
target,
|
|
RuntimeTerritoryTarget::AllTerritories | RuntimeTerritoryTarget::Ids { .. }
|
|
),
|
|
RuntimeEffect::AppendEventRecord { record } => record
|
|
.effects
|
|
.iter()
|
|
.all(runtime_effect_supported_for_save_import),
|
|
}
|
|
}
|
|
|
|
fn runtime_condition_supported_for_save_import(condition: &RuntimeCondition) -> bool {
|
|
match condition {
|
|
RuntimeCondition::WorldVariableThreshold { .. }
|
|
| RuntimeCondition::CompanyNumericThreshold { .. }
|
|
| RuntimeCondition::CompanyVariableThreshold { .. }
|
|
| RuntimeCondition::PlayerVariableThreshold { .. }
|
|
| RuntimeCondition::ChairmanNumericThreshold { .. }
|
|
| RuntimeCondition::TerritoryNumericThreshold { .. }
|
|
| RuntimeCondition::TerritoryVariableThreshold { .. }
|
|
| RuntimeCondition::CompanyTerritoryNumericThreshold { .. }
|
|
| RuntimeCondition::SpecialConditionThreshold { .. }
|
|
| RuntimeCondition::CandidateAvailabilityThreshold { .. }
|
|
| RuntimeCondition::NamedLocomotiveAvailabilityThreshold { .. }
|
|
| RuntimeCondition::NamedLocomotiveCostThreshold { .. }
|
|
| RuntimeCondition::CargoProductionSlotThreshold { .. }
|
|
| RuntimeCondition::CargoProductionTotalThreshold { .. }
|
|
| RuntimeCondition::FactoryProductionTotalThreshold { .. }
|
|
| RuntimeCondition::FarmMineProductionTotalThreshold { .. }
|
|
| RuntimeCondition::OtherCargoProductionTotalThreshold { .. }
|
|
| RuntimeCondition::LimitedTrackBuildingAmountThreshold { .. }
|
|
| RuntimeCondition::TerritoryAccessCostThreshold { .. }
|
|
| RuntimeCondition::EconomicStatusCodeThreshold { .. }
|
|
| RuntimeCondition::WorldFlagEquals { .. } => true,
|
|
}
|
|
}
|
|
|
|
fn build_unsupported_event_runtime_record_summaries(
|
|
live_entry_ids: &[u32],
|
|
note: &str,
|
|
) -> Vec<SmpLoadedPackedEventRecordSummary> {
|
|
live_entry_ids
|
|
.iter()
|
|
.copied()
|
|
.enumerate()
|
|
.map(
|
|
|(record_index, live_entry_id)| SmpLoadedPackedEventRecordSummary {
|
|
record_index,
|
|
live_entry_id,
|
|
payload_offset: None,
|
|
payload_len: None,
|
|
decode_status: "unsupported_framing".to_string(),
|
|
payload_family: "unsupported_framing".to_string(),
|
|
trigger_kind: None,
|
|
active: None,
|
|
marks_collection_dirty: None,
|
|
one_shot: None,
|
|
compact_control: None,
|
|
text_bands: Vec::new(),
|
|
standalone_condition_row_count: 0,
|
|
standalone_condition_rows: Vec::new(),
|
|
negative_sentinel_scope: None,
|
|
grouped_effect_row_counts: vec![0, 0, 0, 0],
|
|
grouped_effect_rows: Vec::new(),
|
|
decoded_conditions: Vec::new(),
|
|
decoded_actions: Vec::new(),
|
|
executable_import_ready: false,
|
|
notes: vec![note.to_string()],
|
|
},
|
|
)
|
|
.collect()
|
|
}
|
|
|
|
fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> SmpInspectionReport {
|
|
let known_tag_hits = KNOWN_TAG_DEFINITIONS
|
|
.iter()
|
|
.filter_map(|definition| {
|
|
let offsets = find_u16_le_offsets(bytes, definition.tag_id);
|
|
if offsets.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
Some(SmpKnownTagHit {
|
|
tag_id: definition.tag_id,
|
|
tag_hex: format!("0x{:04x}", definition.tag_id),
|
|
label: definition.label.to_string(),
|
|
grounded_meaning: definition.grounded_meaning.to_string(),
|
|
hit_count: offsets.len(),
|
|
sample_offsets: offsets
|
|
.iter()
|
|
.copied()
|
|
.take(TAG_OFFSET_SAMPLE_LIMIT)
|
|
.collect(),
|
|
last_offset: offsets.last().copied(),
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let shared_header = parse_shared_header(bytes);
|
|
let header_variant_probe = shared_header.as_ref().map(classify_header_variant_probe);
|
|
let first_ascii_run = find_first_ascii_run(bytes);
|
|
let early_content_probe = first_ascii_run
|
|
.as_ref()
|
|
.and_then(|ascii_run| probe_early_content_layout(bytes, ascii_run));
|
|
let secondary_variant_probe = early_content_probe
|
|
.as_ref()
|
|
.and_then(classify_secondary_variant_probe);
|
|
let container_profile = classify_container_profile(
|
|
file_extension_hint.as_deref(),
|
|
header_variant_probe.as_ref(),
|
|
secondary_variant_probe.as_ref(),
|
|
);
|
|
let runtime_anchor_cycle_block = parse_runtime_anchor_cycle_block(
|
|
bytes,
|
|
container_profile.as_ref(),
|
|
secondary_variant_probe.as_ref(),
|
|
);
|
|
let save_bootstrap_block =
|
|
parse_save_bootstrap_block(container_profile.as_ref(), secondary_variant_probe.as_ref());
|
|
let save_anchor_run_block = parse_save_anchor_run_block(
|
|
bytes,
|
|
container_profile.as_ref(),
|
|
save_bootstrap_block.as_ref(),
|
|
);
|
|
let runtime_trailer_block = parse_runtime_trailer_block(
|
|
container_profile.as_ref(),
|
|
runtime_anchor_cycle_block.as_ref(),
|
|
);
|
|
let runtime_post_span_probe =
|
|
parse_runtime_post_span_probe(bytes, runtime_trailer_block.as_ref());
|
|
let rt3_105_packed_profile_probe = parse_rt3_105_packed_profile_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
header_variant_probe.as_ref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let rt3_105_post_span_bridge_probe = parse_rt3_105_post_span_bridge_probe(
|
|
runtime_trailer_block.as_ref(),
|
|
runtime_post_span_probe.as_ref(),
|
|
rt3_105_packed_profile_probe.as_ref(),
|
|
);
|
|
let rt3_105_save_bridge_payload_probe =
|
|
parse_rt3_105_save_bridge_payload_probe(bytes, rt3_105_post_span_bridge_probe.as_ref());
|
|
let save_world_selection_context_probe = parse_save_world_selection_context_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let save_world_issue_37_probe = parse_save_world_issue_37_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let save_world_economic_tuning_probe = parse_save_world_economic_tuning_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let save_world_finance_neighborhood_probe = parse_save_world_finance_neighborhood_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let save_company_collection_header_probe = parse_save_company_collection_header_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let save_chairman_profile_collection_header_probe =
|
|
parse_save_chairman_profile_collection_header_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let save_train_collection_header_probe = parse_save_train_collection_header_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let save_train_collection_directory_probe = parse_save_train_collection_directory_probe(
|
|
bytes,
|
|
save_train_collection_header_probe.as_ref(),
|
|
);
|
|
let save_region_collection_header_probe = parse_save_region_collection_header_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let save_region_record_triplet_probe =
|
|
parse_save_region_record_triplet_probe(bytes, save_region_collection_header_probe.as_ref());
|
|
let save_region_queued_notice_record_probe = parse_save_region_queued_notice_record_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
save_region_collection_header_probe.as_ref(),
|
|
);
|
|
let save_region_fixed_row_run_candidate_probe = parse_save_region_fixed_row_run_candidate_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
save_region_collection_header_probe.as_ref(),
|
|
);
|
|
let save_placed_structure_collection_header_probe =
|
|
parse_save_placed_structure_collection_header_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let save_placed_structure_record_triplet_probe =
|
|
parse_save_placed_structure_record_triplet_probe(
|
|
bytes,
|
|
save_placed_structure_collection_header_probe.as_ref(),
|
|
);
|
|
let save_placed_structure_dynamic_side_buffer_probe =
|
|
parse_save_placed_structure_dynamic_side_buffer_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let known_header_probes = [
|
|
save_company_collection_header_probe.as_ref(),
|
|
save_chairman_profile_collection_header_probe.as_ref(),
|
|
save_train_collection_header_probe.as_ref(),
|
|
save_region_collection_header_probe.as_ref(),
|
|
save_placed_structure_collection_header_probe.as_ref(),
|
|
];
|
|
let save_unclassified_tagged_collection_header_probes =
|
|
filter_unclassified_tagged_collection_header_probes_outside_known_spans(
|
|
scan_save_unclassified_tagged_collection_header_probes(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
),
|
|
&known_header_probes,
|
|
);
|
|
let save_company_roster_probe = parse_save_company_roster_probe(
|
|
bytes,
|
|
save_company_collection_header_probe.as_ref(),
|
|
save_world_selection_context_probe.as_ref(),
|
|
);
|
|
let save_chairman_profile_table_probe = parse_save_chairman_profile_table_probe(
|
|
bytes,
|
|
save_chairman_profile_collection_header_probe.as_ref(),
|
|
save_world_selection_context_probe.as_ref(),
|
|
save_company_collection_header_probe.as_ref(),
|
|
);
|
|
let rt3_105_save_name_table_probe = parse_rt3_105_save_name_table_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
rt3_105_save_bridge_payload_probe.as_ref(),
|
|
);
|
|
let rt3_105_save_named_locomotive_availability_probe =
|
|
parse_rt3_105_save_named_locomotive_availability_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
rt3_105_packed_profile_probe.as_ref(),
|
|
);
|
|
let special_conditions_probe = parse_special_conditions_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
);
|
|
let smp_aligned_runtime_rule_band_probe = parse_smp_aligned_runtime_rule_band_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
special_conditions_probe.as_ref(),
|
|
);
|
|
let post_special_conditions_scalar_probe = parse_post_special_conditions_scalar_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
special_conditions_probe.as_ref(),
|
|
);
|
|
let post_text_field_neighborhood_probe = parse_post_text_field_neighborhood_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
special_conditions_probe.as_ref(),
|
|
);
|
|
let locomotive_policy_neighborhood_probe = parse_locomotive_policy_neighborhood_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
special_conditions_probe.as_ref(),
|
|
);
|
|
let pre_recipe_scalar_plateau_probe = parse_pre_recipe_scalar_plateau_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
special_conditions_probe.as_ref(),
|
|
);
|
|
let recipe_book_summary_probe = parse_recipe_book_summary_probe(
|
|
bytes,
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
special_conditions_probe.as_ref(),
|
|
);
|
|
let classic_rehydrate_profile_probe =
|
|
parse_classic_rehydrate_profile_probe(bytes, runtime_post_span_probe.as_ref());
|
|
let save_load_summary = build_save_load_summary(
|
|
file_extension_hint.as_deref(),
|
|
container_profile.as_ref(),
|
|
runtime_trailer_block.as_ref(),
|
|
rt3_105_post_span_bridge_probe.as_ref(),
|
|
classic_rehydrate_profile_probe.as_ref(),
|
|
rt3_105_packed_profile_probe.as_ref(),
|
|
rt3_105_save_name_table_probe.as_ref(),
|
|
);
|
|
let event_runtime_collection_summary = parse_event_runtime_collection_summary(
|
|
bytes,
|
|
container_profile.as_ref(),
|
|
save_load_summary.as_ref(),
|
|
);
|
|
let mut warnings = Vec::new();
|
|
if bytes.is_empty() {
|
|
warnings
|
|
.push("File is empty, so no `.smp` container structure could be observed.".to_string());
|
|
}
|
|
|
|
if known_tag_hits.is_empty() {
|
|
warnings.push(
|
|
"No grounded runtime bundle tags were found in little-endian form. This does not prove the file is invalid."
|
|
.to_string(),
|
|
);
|
|
}
|
|
if shared_header.is_none() && !bytes.is_empty() {
|
|
warnings.push(
|
|
"File is shorter than the observed 64-byte common RT3 bundle preamble.".to_string(),
|
|
);
|
|
}
|
|
if let Some(shared_header) = &shared_header {
|
|
let header_family_is_known = header_variant_probe
|
|
.as_ref()
|
|
.map(|probe| probe.is_known_family)
|
|
.unwrap_or(false);
|
|
if !shared_header.matches_grounded_common_signature && !header_family_is_known {
|
|
warnings.push(
|
|
"The first 64-byte preamble does not match the currently observed shared RT3 bundle signature."
|
|
.to_string(),
|
|
);
|
|
}
|
|
}
|
|
if first_ascii_run.is_some() && early_content_probe.is_none() {
|
|
warnings.push(
|
|
"Found early text content but could not resolve the next stable nonzero region after its zero padding."
|
|
.to_string(),
|
|
);
|
|
}
|
|
if container_profile
|
|
.as_ref()
|
|
.is_some_and(|profile| !profile.is_known_profile)
|
|
{
|
|
warnings.push(
|
|
"The current probes did not match any known composite container profile.".to_string(),
|
|
);
|
|
}
|
|
if known_tag_hits
|
|
.iter()
|
|
.any(|hit| hit.hit_count > hit.sample_offsets.len())
|
|
{
|
|
warnings.push(
|
|
"Known-tag offsets are sampled in this report. Large hit counts usually mean byte-pattern noise, not validated chunk boundaries."
|
|
.to_string(),
|
|
);
|
|
}
|
|
warnings.push(
|
|
"Inspection scans raw bytes for a small grounded tag set only. It does not validate bundle layout or decode payloads."
|
|
.to_string(),
|
|
);
|
|
|
|
SmpInspectionReport {
|
|
inspection_mode: "grounded-tag-scan-plus-preamble".to_string(),
|
|
file_extension_hint,
|
|
file_size: bytes.len(),
|
|
sha256: sha256_hex(bytes),
|
|
preamble: parse_preamble(bytes),
|
|
shared_header,
|
|
header_variant_probe,
|
|
first_ascii_run,
|
|
early_content_probe,
|
|
secondary_variant_probe,
|
|
container_profile,
|
|
save_bootstrap_block,
|
|
save_anchor_run_block,
|
|
runtime_anchor_cycle_block,
|
|
runtime_trailer_block,
|
|
runtime_post_span_probe,
|
|
rt3_105_post_span_bridge_probe,
|
|
rt3_105_save_bridge_payload_probe,
|
|
save_world_selection_context_probe,
|
|
save_world_issue_37_probe,
|
|
save_world_economic_tuning_probe,
|
|
save_world_finance_neighborhood_probe,
|
|
save_company_collection_header_probe,
|
|
save_chairman_profile_collection_header_probe,
|
|
save_train_collection_header_probe,
|
|
save_train_collection_directory_probe,
|
|
save_region_collection_header_probe,
|
|
save_region_record_triplet_probe,
|
|
save_region_queued_notice_record_probe,
|
|
save_region_fixed_row_run_candidate_probe,
|
|
save_placed_structure_collection_header_probe,
|
|
save_placed_structure_record_triplet_probe,
|
|
save_placed_structure_dynamic_side_buffer_probe,
|
|
save_unclassified_tagged_collection_header_probes,
|
|
save_company_roster_probe,
|
|
save_chairman_profile_table_probe,
|
|
rt3_105_save_name_table_probe,
|
|
rt3_105_save_named_locomotive_availability_probe,
|
|
special_conditions_probe,
|
|
smp_aligned_runtime_rule_band_probe,
|
|
post_special_conditions_scalar_probe,
|
|
post_text_field_neighborhood_probe,
|
|
locomotive_policy_neighborhood_probe,
|
|
pre_recipe_scalar_plateau_probe,
|
|
recipe_book_summary_probe,
|
|
classic_rehydrate_profile_probe,
|
|
rt3_105_packed_profile_probe,
|
|
save_load_summary,
|
|
event_runtime_collection_summary,
|
|
contains_grounded_runtime_tags: !known_tag_hits.is_empty(),
|
|
known_tag_hits,
|
|
notes: vec![
|
|
"Grounded `.smp` runtime tags currently include mask-plane payload ids 0x2cee and 0x2d51.".to_string(),
|
|
"Grounded sidecar-byte-plane bundle family currently spans 0x9471..0x9472.".to_string(),
|
|
"The shared-header parse is intentionally conservative: it only names common preamble lanes and checks the observed RT3 bundle-family signature.".to_string(),
|
|
"The header-variant probe classifies the preamble into one of the currently observed install-era families when possible."
|
|
.to_string(),
|
|
"The early-content probe resolves the first stable nonzero block after the padded scenario text and then captures the next aligned word window."
|
|
.to_string(),
|
|
"The secondary-variant probe classifies that aligned word window into one of the currently observed file-family patterns."
|
|
.to_string(),
|
|
"The recipe-book summary probe reports per-book structural signatures at the grounded recipe-book root [world+0x0fe7] without attempting a full cargo-line decode."
|
|
.to_string(),
|
|
"Where a recipe cargo-token word looks like two printable letters in its high 16 bits, the probe exposes that as one probable ASCII stem while still treating the wider token semantics as inferred."
|
|
.to_string(),
|
|
"The container-profile layer combines extension hint, header family, and second-window family into one observed container classification."
|
|
.to_string(),
|
|
"The save-bootstrap reader currently parses one conservative 8-word descriptor only for known save-container profiles."
|
|
.to_string(),
|
|
"The save-anchor-run reader follows that descriptor tail into the observed repeated 9-word anchor cycle and captures the first trailer words after the cycle diverges."
|
|
.to_string(),
|
|
"The runtime-anchor-cycle reader applies the same cycle/trailer scan across the currently known save and sandbox runtime container profiles."
|
|
.to_string(),
|
|
"The runtime-trailer reader classifies the first 16 words after the cycle divergence into the currently observed runtime trailer families."
|
|
.to_string(),
|
|
"The runtime post-span probe follows the trailer's high-16 span lane into the later file region and records the next nonzero bytes, the first aligned high-16-dense candidate window, and any grounded progress-id hits found nearby."
|
|
.to_string(),
|
|
"The RT3 1.05 post-span bridge probe correlates the trailer selector/descriptor lanes with the next candidate region and the later packed-profile block for the currently observed 1.05 save families."
|
|
.to_string(),
|
|
"The RT3 1.05 common-save bridge payload probe captures the two stable bridge-stage blocks currently observed under the base 1.05 save branch."
|
|
.to_string(),
|
|
"The RT3 1.05 candidate-availability table probe decodes the fixed-width trailing name table from either the common-save bridge payload or the fixed 0x6a70..0x73c0 source range when that header validates."
|
|
.to_string(),
|
|
"The RT3 1.05 save-side named locomotive availability probe scans the post-profile save region for the grounded fixed-width locomotive-name-plus-dword row family when that run is present."
|
|
.to_string(),
|
|
"The post-special-conditions scalar probe captures the fixed 0x0df4..0x0f30 dword window immediately after the hidden sentinel slot, splits it into the aligned-band overlap prefix and the later tail, and records the live-object offset alignment of that tail without claiming a byte-for-byte mirror."
|
|
.to_string(),
|
|
"The classic rehydrate-profile probe recognizes the grounded 0x32dc -> 0x3714 -> 0x3715 progress-id sequence and captures the exact 0x108-byte block between the latter two ids when that pattern appears."
|
|
.to_string(),
|
|
"The classic packed-profile block reader exposes the stable map-path, display-name, atlas-tracked latch bytes, and the small set of nonzero word lanes observed inside that 0x108-byte block."
|
|
.to_string(),
|
|
"The RT3 1.05 packed-profile probe recognizes the later string-bearing save block rooted at the first post-header .gmp path and exposes the observed map-path, display-name, atlas-tracked byte lanes, and stable nonzero words."
|
|
.to_string(),
|
|
format!(
|
|
"Restore-side loading of the four sidecar byte planes is only grounded for bundle versions >= 0x{:04x}.",
|
|
SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION
|
|
),
|
|
],
|
|
warnings,
|
|
}
|
|
}
|
|
|
|
fn build_save_load_summary(
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
runtime_trailer_block: Option<&SmpRuntimeTrailerBlock>,
|
|
rt3_105_post_span_bridge_probe: Option<&SmpRt3105PostSpanBridgeProbe>,
|
|
classic_rehydrate_profile_probe: Option<&SmpClassicRehydrateProfileProbe>,
|
|
rt3_105_packed_profile_probe: Option<&SmpRt3105PackedProfileProbe>,
|
|
rt3_105_save_name_table_probe: Option<&SmpRt3105SaveNameTableProbe>,
|
|
) -> Option<SmpSaveLoadSummary> {
|
|
let file_extension_hint = file_extension_hint.map(str::to_string);
|
|
let container_profile_family = container_profile.map(|profile| profile.profile_family.clone());
|
|
let trailer_family = runtime_trailer_block.map(|trailer| trailer.trailer_family.clone());
|
|
let bridge_family = rt3_105_post_span_bridge_probe.map(|bridge| bridge.bridge_family.clone());
|
|
let candidate_table =
|
|
rt3_105_save_name_table_probe.map(|probe| SmpSaveLoadCandidateTableSummary {
|
|
source_kind: probe.source_kind.clone(),
|
|
semantic_family: probe.semantic_family.clone(),
|
|
observed_entry_count: probe.observed_entry_count,
|
|
zero_availability_count: probe.zero_trailer_entry_count,
|
|
zero_availability_names: probe.zero_trailer_entry_names.clone(),
|
|
footer_progress_hex_words: vec![
|
|
probe.footer_progress_word_0_hex.clone(),
|
|
probe.footer_progress_word_1_hex.clone(),
|
|
],
|
|
});
|
|
|
|
if let Some(probe) = classic_rehydrate_profile_probe {
|
|
let block = &probe.packed_profile_block;
|
|
let mut notes = vec![
|
|
"Classic save load reaches the grounded late rehydrate band 0x32dc -> 0x3714 -> 0x3715."
|
|
.to_string(),
|
|
"The file exposes one exact 0x108 packed-profile block between progress ids 0x3714 and 0x3715."
|
|
.to_string(),
|
|
];
|
|
if let Some(map_path) = &block.map_path {
|
|
notes.push(format!("Packed profile map path: {map_path}"));
|
|
}
|
|
if let Some(display_name) = &block.display_name {
|
|
notes.push(format!("Packed profile display name: {display_name}"));
|
|
}
|
|
|
|
return Some(SmpSaveLoadSummary {
|
|
file_extension_hint,
|
|
container_profile_family,
|
|
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
|
mechanism_confidence: "grounded".to_string(),
|
|
packed_profile_kind: Some("classic-rehydrate-profile".to_string()),
|
|
packed_profile_family: Some(probe.profile_family.clone()),
|
|
packed_profile_offset: Some(probe.packed_profile_offset),
|
|
packed_profile_len: Some(probe.packed_profile_len),
|
|
map_path: block.map_path.clone(),
|
|
display_name: block.display_name.clone(),
|
|
profile_byte_0x77: Some(block.profile_byte_0x77),
|
|
profile_byte_0x77_hex: Some(block.profile_byte_0x77_hex.clone()),
|
|
profile_byte_0x82: Some(block.profile_byte_0x82),
|
|
profile_byte_0x82_hex: Some(block.profile_byte_0x82_hex.clone()),
|
|
profile_byte_0x97: Some(block.profile_byte_0x97),
|
|
profile_byte_0x97_hex: Some(block.profile_byte_0x97_hex.clone()),
|
|
profile_byte_0xc5: Some(block.profile_byte_0xc5),
|
|
profile_byte_0xc5_hex: Some(block.profile_byte_0xc5_hex.clone()),
|
|
trailer_family,
|
|
bridge_family: None,
|
|
candidate_table,
|
|
notes,
|
|
});
|
|
}
|
|
|
|
if let Some(probe) = rt3_105_packed_profile_probe {
|
|
let block = &probe.packed_profile_block;
|
|
let mechanism_family = rt3_105_post_span_bridge_probe
|
|
.map(|bridge| bridge.bridge_family.clone())
|
|
.unwrap_or_else(|| match probe.profile_family.as_str() {
|
|
"rt3-105-scenario-save-container-v1" => {
|
|
"rt3-105-scenario-save-profile-analog-v1".to_string()
|
|
}
|
|
"rt3-105-alt-save-container-v1" => "rt3-105-alt-save-profile-analog-v1".to_string(),
|
|
_ => "rt3-105-save-profile-analog-v1".to_string(),
|
|
});
|
|
let mechanism_confidence = if rt3_105_post_span_bridge_probe.is_some() {
|
|
"mixed"
|
|
} else {
|
|
"inferred"
|
|
}
|
|
.to_string();
|
|
let mut notes = Vec::new();
|
|
if let Some(bridge) = rt3_105_post_span_bridge_probe {
|
|
notes.push(format!(
|
|
"RT3 1.05 save branch uses {} with selector/descriptor {} -> {}.",
|
|
bridge.bridge_family, bridge.selector_high_hex, bridge.descriptor_high_hex
|
|
));
|
|
} else {
|
|
notes.push(
|
|
"RT3 1.05 save exposes a packed-profile analogue, but the upstream load bridge is not resolved for this branch."
|
|
.to_string(),
|
|
);
|
|
}
|
|
if let Some(map_path) = &block.map_path {
|
|
notes.push(format!("Packed profile map path: {map_path}"));
|
|
}
|
|
if let Some(display_name) = &block.display_name {
|
|
notes.push(format!("Packed profile display name: {display_name}"));
|
|
}
|
|
if let Some(table) = &candidate_table {
|
|
notes.push(format!(
|
|
"Candidate table source {} carries {} entries with {} zero-availability overrides.",
|
|
table.source_kind, table.observed_entry_count, table.zero_availability_count
|
|
));
|
|
}
|
|
|
|
return Some(SmpSaveLoadSummary {
|
|
file_extension_hint,
|
|
container_profile_family,
|
|
mechanism_family,
|
|
mechanism_confidence,
|
|
packed_profile_kind: Some("rt3-105-packed-profile".to_string()),
|
|
packed_profile_family: Some(probe.profile_family.clone()),
|
|
packed_profile_offset: Some(probe.packed_profile_offset),
|
|
packed_profile_len: Some(probe.packed_profile_len),
|
|
map_path: block.map_path.clone(),
|
|
display_name: block.display_name.clone(),
|
|
profile_byte_0x77: Some(block.profile_byte_0x77),
|
|
profile_byte_0x77_hex: Some(block.profile_byte_0x77_hex.clone()),
|
|
profile_byte_0x82: Some(block.profile_byte_0x82),
|
|
profile_byte_0x82_hex: Some(block.profile_byte_0x82_hex.clone()),
|
|
profile_byte_0x97: Some(block.profile_byte_0x97),
|
|
profile_byte_0x97_hex: Some(block.profile_byte_0x97_hex.clone()),
|
|
profile_byte_0xc5: Some(block.profile_byte_0xc5),
|
|
profile_byte_0xc5_hex: Some(block.profile_byte_0xc5_hex.clone()),
|
|
trailer_family,
|
|
bridge_family,
|
|
candidate_table,
|
|
notes,
|
|
});
|
|
}
|
|
|
|
if let Some(table) = candidate_table {
|
|
return Some(SmpSaveLoadSummary {
|
|
file_extension_hint,
|
|
container_profile_family,
|
|
mechanism_family: "rt3-105-candidate-catalog-source-v1".to_string(),
|
|
mechanism_confidence: "mixed".to_string(),
|
|
packed_profile_kind: None,
|
|
packed_profile_family: None,
|
|
packed_profile_offset: None,
|
|
packed_profile_len: None,
|
|
map_path: None,
|
|
display_name: None,
|
|
profile_byte_0x77: None,
|
|
profile_byte_0x77_hex: None,
|
|
profile_byte_0x82: None,
|
|
profile_byte_0x82_hex: None,
|
|
profile_byte_0x97: None,
|
|
profile_byte_0x97_hex: None,
|
|
profile_byte_0xc5: None,
|
|
profile_byte_0xc5_hex: None,
|
|
trailer_family,
|
|
bridge_family,
|
|
notes: vec![
|
|
format!(
|
|
"The file carries the shared 1.05 candidate table source block through {}.",
|
|
table.source_kind
|
|
),
|
|
format!(
|
|
"The table exposes {} named entries with {} zero-availability overrides.",
|
|
table.observed_entry_count, table.zero_availability_count
|
|
),
|
|
],
|
|
candidate_table: Some(table),
|
|
});
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn parse_preamble(bytes: &[u8]) -> SmpPreamble {
|
|
let byte_len = bytes.len().min(PREAMBLE_U32_WORD_COUNT * 4);
|
|
let words = bytes[..byte_len]
|
|
.chunks_exact(4)
|
|
.enumerate()
|
|
.map(|(index, chunk)| {
|
|
let value_le = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
|
|
SmpPreambleWord {
|
|
index,
|
|
offset: index * 4,
|
|
value_le,
|
|
value_hex: format!("0x{value_le:08x}"),
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
SmpPreamble {
|
|
byte_len,
|
|
word_count: words.len(),
|
|
words,
|
|
}
|
|
}
|
|
|
|
fn parse_shared_header(bytes: &[u8]) -> Option<SmpSharedHeader> {
|
|
let words = read_preamble_words(bytes)?;
|
|
let shared_signature_words_1_to_7 = words[1..=7].to_vec();
|
|
let payload_window_words_8_to_9 = words[8..=9].to_vec();
|
|
let reserved_words_10_to_14 = words[10..=14].to_vec();
|
|
let final_flag_word = words[15];
|
|
|
|
Some(SmpSharedHeader {
|
|
byte_len: PREAMBLE_U32_WORD_COUNT * 4,
|
|
root_kind_word: words[0],
|
|
root_kind_word_hex: format!("0x{:08x}", words[0]),
|
|
primary_family_tag: words[1],
|
|
primary_family_tag_hex: format!("0x{:08x}", words[1]),
|
|
shared_signature_hex_words_1_to_7: shared_signature_words_1_to_7
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect(),
|
|
matches_grounded_common_signature: shared_signature_words_1_to_7
|
|
== SHARED_SIGNATURE_WORDS_1_TO_7,
|
|
shared_signature_words_1_to_7,
|
|
payload_window_hex_words_8_to_9: payload_window_words_8_to_9
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect(),
|
|
payload_window_words_8_to_9,
|
|
reserved_words_10_to_14_all_zero: reserved_words_10_to_14.iter().all(|word| *word == 0),
|
|
reserved_words_10_to_14,
|
|
final_flag_word,
|
|
final_flag_word_hex: format!("0x{final_flag_word:08x}"),
|
|
})
|
|
}
|
|
|
|
fn classify_header_variant_probe(shared_header: &SmpSharedHeader) -> SmpHeaderVariantProbe {
|
|
let words = &shared_header.shared_signature_words_1_to_7;
|
|
let root = shared_header.root_kind_word;
|
|
let final_flag = shared_header.final_flag_word;
|
|
|
|
let (variant_family, evidence, is_known_family) = match (root, words.as_slice(), final_flag) {
|
|
(
|
|
0x00002649,
|
|
[
|
|
0x00002ee0,
|
|
0x00040001,
|
|
0x00028000,
|
|
0x00010000,
|
|
0x00000771,
|
|
0x00000771,
|
|
0x00000771,
|
|
],
|
|
0x00000001,
|
|
) => (
|
|
"rt3-105-gmx-header-v1".to_string(),
|
|
vec![
|
|
"root kind word 0x00002649".to_string(),
|
|
"1.05 common signature words 1..7".to_string(),
|
|
"final flag 0x00000001".to_string(),
|
|
],
|
|
true,
|
|
),
|
|
(
|
|
0x000025e5,
|
|
[
|
|
0x00002ee0,
|
|
0x00040001,
|
|
0x00028000,
|
|
0x00010000,
|
|
0x00000771,
|
|
0x00000771,
|
|
0x00000771,
|
|
],
|
|
0x00000000,
|
|
) => (
|
|
"rt3-105-common-header-v1".to_string(),
|
|
vec![
|
|
"root kind word 0x000025e5".to_string(),
|
|
"1.05 common signature words 1..7".to_string(),
|
|
"final flag 0x00000000".to_string(),
|
|
],
|
|
true,
|
|
),
|
|
(
|
|
0x000025e5,
|
|
[
|
|
0x00002ee0,
|
|
0x00040001,
|
|
0x00018000,
|
|
0x00010000,
|
|
0x00000746,
|
|
0x00000746,
|
|
0x00000746,
|
|
],
|
|
0x00000000,
|
|
) => (
|
|
"rt3-105-scenario-save-header-v1".to_string(),
|
|
vec![
|
|
"root kind word 0x000025e5".to_string(),
|
|
"1.05 scenario-save signature words 1..7".to_string(),
|
|
"final flag 0x00000000".to_string(),
|
|
],
|
|
true,
|
|
),
|
|
(
|
|
0x000025e5,
|
|
[
|
|
0x00002ee0,
|
|
0x0001c001,
|
|
0x00018000,
|
|
0x00010000,
|
|
0x00000754,
|
|
0x00000754,
|
|
0x00000754,
|
|
],
|
|
0x00000000,
|
|
) => (
|
|
"rt3-105-alt-save-header-v1".to_string(),
|
|
vec![
|
|
"root kind word 0x000025e5".to_string(),
|
|
"1.05 alternate-save signature words 1..7".to_string(),
|
|
"final flag 0x00000000".to_string(),
|
|
],
|
|
true,
|
|
),
|
|
(
|
|
0x000026ad,
|
|
[
|
|
0x00002ee0,
|
|
0x00014001,
|
|
0x00020000,
|
|
0x00010000,
|
|
0x00000725,
|
|
0x00000725,
|
|
0x00000725,
|
|
],
|
|
0x00000100,
|
|
) => (
|
|
"rt3-classic-gms-header-v1".to_string(),
|
|
vec![
|
|
"root kind word 0x000026ad".to_string(),
|
|
"classic save signature words 1..7".to_string(),
|
|
"final flag 0x00000100".to_string(),
|
|
],
|
|
true,
|
|
),
|
|
(
|
|
0x000026ad,
|
|
[
|
|
0x00002ee0,
|
|
0x0001c001,
|
|
0x00018000,
|
|
0x00010000,
|
|
0x00000765,
|
|
0x00000765,
|
|
0x00000765,
|
|
],
|
|
0x00000001,
|
|
) => (
|
|
"rt3-classic-gmx-header-v1".to_string(),
|
|
vec![
|
|
"root kind word 0x000026ad".to_string(),
|
|
"classic sandbox signature words 1..7".to_string(),
|
|
"final flag 0x00000001".to_string(),
|
|
],
|
|
true,
|
|
),
|
|
(0x000025e5, [0x00002ee0, _, _, 0x00010000, _, _, _], 0x00000000 | 0x00000100) => (
|
|
"rt3-map-header-family".to_string(),
|
|
vec![
|
|
"root kind word 0x000025e5".to_string(),
|
|
"map-family anchor 0x00002ee0".to_string(),
|
|
"word4 0x00010000".to_string(),
|
|
],
|
|
true,
|
|
),
|
|
_ => (
|
|
"unknown".to_string(),
|
|
vec![format!(
|
|
"root=0x{root:08x}, words1..7={}, final=0x{final_flag:08x}",
|
|
words
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
)],
|
|
false,
|
|
),
|
|
};
|
|
|
|
SmpHeaderVariantProbe {
|
|
variant_family,
|
|
variant_evidence: evidence,
|
|
is_known_family,
|
|
}
|
|
}
|
|
|
|
fn read_preamble_words(bytes: &[u8]) -> Option<[u32; PREAMBLE_U32_WORD_COUNT]> {
|
|
if bytes.len() < PREAMBLE_U32_WORD_COUNT * 4 {
|
|
return None;
|
|
}
|
|
|
|
let mut words = [0u32; PREAMBLE_U32_WORD_COUNT];
|
|
for (index, chunk) in bytes[..PREAMBLE_U32_WORD_COUNT * 4]
|
|
.chunks_exact(4)
|
|
.enumerate()
|
|
{
|
|
words[index] = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
|
|
}
|
|
Some(words)
|
|
}
|
|
|
|
fn probe_early_content_layout(
|
|
bytes: &[u8],
|
|
ascii_run: &SmpAsciiPreview,
|
|
) -> Option<SmpEarlyContentProbe> {
|
|
let search_start = ascii_run.offset + ascii_run.byte_len;
|
|
let first_post_text_nonzero_offset = find_next_nonzero_offset(bytes, search_start)?;
|
|
let zero_pad_after_text_len = first_post_text_nonzero_offset.saturating_sub(search_start);
|
|
let first_zero_run_after_block = find_zero_run(
|
|
bytes,
|
|
first_post_text_nonzero_offset,
|
|
EARLY_ZERO_RUN_THRESHOLD,
|
|
)
|
|
.unwrap_or(bytes.len());
|
|
let first_post_text_block = &bytes[first_post_text_nonzero_offset..first_zero_run_after_block];
|
|
let secondary_nonzero_offset = find_next_nonzero_offset(bytes, first_zero_run_after_block);
|
|
let trailing_zero_pad_after_first_block_len = secondary_nonzero_offset
|
|
.map(|offset| offset.saturating_sub(first_zero_run_after_block))
|
|
.unwrap_or_else(|| bytes.len().saturating_sub(first_zero_run_after_block));
|
|
let secondary_aligned_word_window_offset = secondary_nonzero_offset.map(|offset| offset & !0x3);
|
|
let secondary_aligned_word_window_words = secondary_aligned_word_window_offset
|
|
.map(|offset| read_u32_window(bytes, offset, EARLY_ALIGNED_WORD_WINDOW_COUNT))
|
|
.unwrap_or_default();
|
|
let secondary_preview_hex = secondary_nonzero_offset
|
|
.map(|offset| {
|
|
hex_encode(&bytes[offset..bytes.len().min(offset + EARLY_PREVIEW_BYTE_LIMIT)])
|
|
})
|
|
.unwrap_or_default();
|
|
|
|
Some(SmpEarlyContentProbe {
|
|
first_post_text_nonzero_offset,
|
|
zero_pad_after_text_len,
|
|
first_post_text_block_len: first_post_text_block.len(),
|
|
first_post_text_block_hex: hex_encode(first_post_text_block),
|
|
trailing_zero_pad_after_first_block_len,
|
|
secondary_nonzero_offset,
|
|
secondary_aligned_word_window_offset,
|
|
secondary_aligned_word_window_hex_words: secondary_aligned_word_window_words
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect(),
|
|
secondary_aligned_word_window_words,
|
|
secondary_preview_hex,
|
|
})
|
|
}
|
|
|
|
fn classify_secondary_variant_probe(
|
|
probe: &SmpEarlyContentProbe,
|
|
) -> Option<SmpSecondaryVariantProbe> {
|
|
let aligned_window_offset = probe.secondary_aligned_word_window_offset?;
|
|
let words = probe.secondary_aligned_word_window_words.clone();
|
|
if words.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let mut evidence = Vec::new();
|
|
let variant_family = match words.as_slice() {
|
|
[0x001e0000, 0x86a00100, 0x03000001, 0xf0000100, ..] => {
|
|
evidence.push("leading word 0x001e0000".to_string());
|
|
evidence.push("anchor word 0x86a00100".to_string());
|
|
evidence.push("third/fourth words 0x03000001 and 0xf0000100".to_string());
|
|
"rt3-gms-family-v1".to_string()
|
|
}
|
|
[0x000a0000, 0x49f00100, 0x00000002, 0xa0000000, ..] => {
|
|
evidence.push("leading word 0x000a0000".to_string());
|
|
evidence.push("anchor word 0x49f00100".to_string());
|
|
evidence.push("third/fourth words 0x00000002 and 0xa0000000".to_string());
|
|
"rt3-gmx-family-v1".to_string()
|
|
}
|
|
[0x001c0000, 0x86a00100, 0x00000001, 0xa0000000, ..] => {
|
|
evidence.push("leading word 0x001c0000".to_string());
|
|
evidence.push("anchor word 0x86a00100".to_string());
|
|
evidence.push("third/fourth words 0x00000001 and 0xa0000000".to_string());
|
|
"rt3-105-gms-family-v1".to_string()
|
|
}
|
|
[0x00190000, 0x86a00100, 0x00000001, 0xa0000000, ..] => {
|
|
evidence.push("leading word 0x00190000".to_string());
|
|
evidence.push("anchor word 0x86a00100".to_string());
|
|
evidence.push("third/fourth words 0x00000001 and 0xa0000000".to_string());
|
|
"rt3-105-gmx-family-v1".to_string()
|
|
}
|
|
[0x00130000, 0x86a00100, 0x21000001, 0xa0000100, ..] => {
|
|
evidence.push("leading word 0x00130000".to_string());
|
|
evidence.push("anchor word 0x86a00100".to_string());
|
|
evidence.push("third/fourth words 0x21000001 and 0xa0000100".to_string());
|
|
"rt3-105-gms-scenario-family-v1".to_string()
|
|
}
|
|
[0x00140000, 0x93e00100, 0x00000004, 0xa0000000, ..] => {
|
|
evidence.push("leading word 0x00140000".to_string());
|
|
evidence.push("anchor word 0x93e00100".to_string());
|
|
evidence.push("third/fourth words 0x00000004 and 0xa0000000".to_string());
|
|
"rt3-map-secondary-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,
|
|
),
|
|
("gmp", "rt3-map-header-family", "rt3-map-secondary-family-v1") => (
|
|
"rt3-map-container-family".to_string(),
|
|
vec![
|
|
"extension .gmp".to_string(),
|
|
"map header family".to_string(),
|
|
"observed map secondary window family".to_string(),
|
|
],
|
|
true,
|
|
),
|
|
(_, header_family, secondary_family) => (
|
|
"unknown".to_string(),
|
|
vec![
|
|
format!(
|
|
"extension {}",
|
|
if extension.is_empty() {
|
|
"<none>"
|
|
} else {
|
|
extension
|
|
}
|
|
),
|
|
format!("header family {header_family}"),
|
|
format!("secondary family {secondary_family}"),
|
|
],
|
|
false,
|
|
),
|
|
};
|
|
|
|
Some(SmpContainerProfile {
|
|
profile_family,
|
|
profile_evidence,
|
|
is_known_profile,
|
|
})
|
|
}
|
|
|
|
fn parse_save_bootstrap_block(
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
secondary_variant_probe: Option<&SmpSecondaryVariantProbe>,
|
|
) -> Option<SmpSaveBootstrapBlock> {
|
|
let profile = container_profile?;
|
|
let secondary = secondary_variant_probe?;
|
|
let words = &secondary.words;
|
|
if words.len() < 8 {
|
|
return None;
|
|
}
|
|
|
|
let supported = matches!(
|
|
profile.profile_family.as_str(),
|
|
"rt3-classic-save-container-v1"
|
|
| "rt3-105-save-container-v1"
|
|
| "rt3-105-scenario-save-container-v1"
|
|
| "rt3-105-alt-save-container-v1"
|
|
);
|
|
if !supported {
|
|
return None;
|
|
}
|
|
|
|
Some(SmpSaveBootstrapBlock {
|
|
profile_family: profile.profile_family.clone(),
|
|
aligned_window_offset: secondary.aligned_window_offset,
|
|
leading_word: words[0],
|
|
leading_word_hex: format!("0x{:08x}", words[0]),
|
|
anchor_word: words[1],
|
|
anchor_word_hex: format!("0x{:08x}", words[1]),
|
|
descriptor_word_2: words[2],
|
|
descriptor_word_2_hex: format!("0x{:08x}", words[2]),
|
|
descriptor_word_3: words[3],
|
|
descriptor_word_3_hex: format!("0x{:08x}", words[3]),
|
|
descriptor_word_4: words[4],
|
|
descriptor_word_4_hex: format!("0x{:08x}", words[4]),
|
|
descriptor_word_5: words[5],
|
|
descriptor_word_5_hex: format!("0x{:08x}", words[5]),
|
|
descriptor_word_6: words[6],
|
|
descriptor_word_6_hex: format!("0x{:08x}", words[6]),
|
|
descriptor_word_7: words[7],
|
|
descriptor_word_7_hex: format!("0x{:08x}", words[7]),
|
|
})
|
|
}
|
|
|
|
fn parse_runtime_anchor_cycle_block(
|
|
bytes: &[u8],
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
secondary_variant_probe: Option<&SmpSecondaryVariantProbe>,
|
|
) -> Option<SmpRuntimeAnchorCycleBlock> {
|
|
let profile = container_profile?;
|
|
let secondary = secondary_variant_probe?;
|
|
let supported = matches!(
|
|
profile.profile_family.as_str(),
|
|
"rt3-classic-save-container-v1"
|
|
| "rt3-classic-sandbox-container-v1"
|
|
| "rt3-105-save-container-v1"
|
|
| "rt3-105-scenario-save-container-v1"
|
|
| "rt3-105-alt-save-container-v1"
|
|
| "rt3-105-sandbox-container-v1"
|
|
);
|
|
if !supported {
|
|
return None;
|
|
}
|
|
|
|
let cycle_start_offset = secondary.aligned_window_offset + 0x1c;
|
|
let cycle_words = read_u32_window(bytes, cycle_start_offset, 9);
|
|
if cycle_words.len() < 9 {
|
|
return None;
|
|
}
|
|
|
|
let mut full_cycle_count = 0usize;
|
|
let mut cursor = cycle_start_offset;
|
|
while read_u32_window(bytes, cursor, cycle_words.len()) == cycle_words {
|
|
full_cycle_count += 1;
|
|
cursor += cycle_words.len() * 4;
|
|
}
|
|
|
|
if full_cycle_count == 0 {
|
|
return None;
|
|
}
|
|
|
|
let mut partial_cycle_word_count = 0usize;
|
|
while partial_cycle_word_count < cycle_words.len() {
|
|
let offset = cursor + partial_cycle_word_count * 4;
|
|
if read_u32_at(bytes, offset) == Some(cycle_words[partial_cycle_word_count]) {
|
|
partial_cycle_word_count += 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
let trailer_offset = cursor + partial_cycle_word_count * 4;
|
|
let trailer_words = read_u32_window(bytes, trailer_offset, 16);
|
|
|
|
Some(SmpRuntimeAnchorCycleBlock {
|
|
profile_family: profile.profile_family.clone(),
|
|
cycle_start_offset,
|
|
cycle_hex_words: cycle_words
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect(),
|
|
cycle_words,
|
|
full_cycle_count,
|
|
partial_cycle_word_count,
|
|
trailer_offset,
|
|
trailer_hex_words: trailer_words
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect(),
|
|
trailer_words,
|
|
})
|
|
}
|
|
|
|
fn parse_save_anchor_run_block(
|
|
bytes: &[u8],
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
save_bootstrap_block: Option<&SmpSaveBootstrapBlock>,
|
|
) -> Option<SmpSaveAnchorRunBlock> {
|
|
let profile = container_profile?;
|
|
let bootstrap = save_bootstrap_block?;
|
|
let supported = matches!(
|
|
profile.profile_family.as_str(),
|
|
"rt3-classic-save-container-v1"
|
|
| "rt3-105-save-container-v1"
|
|
| "rt3-105-scenario-save-container-v1"
|
|
| "rt3-105-alt-save-container-v1"
|
|
);
|
|
if !supported {
|
|
return None;
|
|
}
|
|
|
|
let cycle_start_offset = bootstrap.aligned_window_offset + 0x1c;
|
|
let cycle_words = read_u32_window(bytes, cycle_start_offset, 9);
|
|
if cycle_words.len() < 9 {
|
|
return None;
|
|
}
|
|
|
|
let mut full_cycle_count = 0usize;
|
|
let mut cursor = cycle_start_offset;
|
|
while read_u32_window(bytes, cursor, cycle_words.len()) == cycle_words {
|
|
full_cycle_count += 1;
|
|
cursor += cycle_words.len() * 4;
|
|
}
|
|
|
|
if full_cycle_count == 0 {
|
|
return None;
|
|
}
|
|
|
|
let mut partial_cycle_word_count = 0usize;
|
|
while partial_cycle_word_count < cycle_words.len() {
|
|
let offset = cursor + partial_cycle_word_count * 4;
|
|
if read_u32_at(bytes, offset) == Some(cycle_words[partial_cycle_word_count]) {
|
|
partial_cycle_word_count += 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
let trailer_offset = cursor + partial_cycle_word_count * 4;
|
|
let trailer_words = read_u32_window(bytes, trailer_offset, 12);
|
|
|
|
Some(SmpSaveAnchorRunBlock {
|
|
profile_family: profile.profile_family.clone(),
|
|
cycle_start_offset,
|
|
cycle_hex_words: cycle_words
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect(),
|
|
cycle_words,
|
|
full_cycle_count,
|
|
partial_cycle_word_count,
|
|
trailer_offset,
|
|
trailer_hex_words: trailer_words
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect(),
|
|
trailer_words,
|
|
})
|
|
}
|
|
|
|
fn parse_runtime_trailer_block(
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
runtime_anchor_cycle_block: Option<&SmpRuntimeAnchorCycleBlock>,
|
|
) -> Option<SmpRuntimeTrailerBlock> {
|
|
let profile = container_profile?;
|
|
let anchor = runtime_anchor_cycle_block?;
|
|
let words = &anchor.trailer_words;
|
|
if words.len() < 16 {
|
|
return None;
|
|
}
|
|
|
|
let trailer_family = match profile.profile_family.as_str() {
|
|
"rt3-classic-save-container-v1"
|
|
if words[..6]
|
|
== [
|
|
0x00020000, 0x00030000, 0x00010000, 0x00010000, 0x00010000, 0x00020000,
|
|
] =>
|
|
{
|
|
"rt3-classic-save-trailer-v1"
|
|
}
|
|
"rt3-classic-sandbox-container-v1"
|
|
if words[..6]
|
|
== [
|
|
0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00000000, 0x00000000,
|
|
] =>
|
|
{
|
|
"rt3-classic-sandbox-trailer-v1"
|
|
}
|
|
"rt3-105-save-container-v1"
|
|
| "rt3-105-scenario-save-container-v1"
|
|
| "rt3-105-alt-save-container-v1"
|
|
if words[..6]
|
|
== [
|
|
0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00000000, 0x00000000,
|
|
] =>
|
|
{
|
|
"rt3-105-save-trailer-v1"
|
|
}
|
|
"rt3-105-sandbox-container-v1"
|
|
if words[..6]
|
|
== [
|
|
0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00000000, 0x00000000,
|
|
] =>
|
|
{
|
|
"rt3-105-sandbox-trailer-v1"
|
|
}
|
|
_ => "unknown",
|
|
}
|
|
.to_string();
|
|
|
|
let tag_chunk_id_u16 = (words[6] >> 16) as u16;
|
|
let length_high_u16 = (words[7] >> 16) as u16;
|
|
let selector_high_u16 = (words[8] >> 16) as u16;
|
|
let descriptor_high_u16 = (words[10] >> 16) as u16;
|
|
let tag_chunk_id_grounded_alignment =
|
|
classify_runtime_trailer_chunk_id_grounded_alignment(tag_chunk_id_u16).map(str::to_string);
|
|
|
|
let mut trailer_evidence = vec![
|
|
format!("container profile {}", profile.profile_family),
|
|
format!(
|
|
"prefix words {}",
|
|
words[..6]
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
),
|
|
format!("high-16 chunk id 0x{tag_chunk_id_u16:04x} from trailer word 6"),
|
|
format!("high-16 span 0x{length_high_u16:04x} from trailer word 7"),
|
|
format!("high-16 selector 0x{selector_high_u16:04x} from trailer word 8"),
|
|
format!("high-16 descriptor 0x{descriptor_high_u16:04x} from trailer word 10"),
|
|
];
|
|
if let Some(alignment) = &tag_chunk_id_grounded_alignment {
|
|
trailer_evidence.push(alignment.clone());
|
|
}
|
|
|
|
Some(SmpRuntimeTrailerBlock {
|
|
profile_family: profile.profile_family.clone(),
|
|
trailer_family,
|
|
trailer_evidence,
|
|
trailer_offset: anchor.trailer_offset,
|
|
prefix_words_0_to_5: words[..6].to_vec(),
|
|
prefix_hex_words_0_to_5: words[..6]
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect(),
|
|
tag_word_6: words[6],
|
|
tag_word_6_hex: format!("0x{:08x}", words[6]),
|
|
tag_chunk_id_u16,
|
|
tag_chunk_id_hex: format!("0x{tag_chunk_id_u16:04x}"),
|
|
tag_chunk_id_grounded_alignment,
|
|
length_word_7: words[7],
|
|
length_word_7_hex: format!("0x{:08x}", words[7]),
|
|
length_high_u16,
|
|
length_high_hex: format!("0x{length_high_u16:04x}"),
|
|
selector_word_8: words[8],
|
|
selector_word_8_hex: format!("0x{:08x}", words[8]),
|
|
selector_high_u16,
|
|
selector_high_hex: format!("0x{selector_high_u16:04x}"),
|
|
layout_word_9: words[9],
|
|
layout_word_9_hex: format!("0x{:08x}", words[9]),
|
|
descriptor_word_10: words[10],
|
|
descriptor_word_10_hex: format!("0x{:08x}", words[10]),
|
|
descriptor_high_u16,
|
|
descriptor_high_hex: format!("0x{descriptor_high_u16:04x}"),
|
|
descriptor_word_11: words[11],
|
|
descriptor_word_11_hex: format!("0x{:08x}", words[11]),
|
|
counter_word_12: words[12],
|
|
counter_word_12_hex: format!("0x{:08x}", words[12]),
|
|
offset_word_13: words[13],
|
|
offset_word_13_hex: format!("0x{:08x}", words[13]),
|
|
span_word_14: words[14],
|
|
span_word_14_hex: format!("0x{:08x}", words[14]),
|
|
mode_word_15: words[15],
|
|
mode_word_15_hex: format!("0x{:08x}", words[15]),
|
|
words: words.to_vec(),
|
|
hex_words: words.iter().map(|word| format!("0x{word:08x}")).collect(),
|
|
})
|
|
}
|
|
|
|
fn classify_runtime_trailer_chunk_id_grounded_alignment(
|
|
tag_chunk_id_u16: u16,
|
|
) -> Option<&'static str> {
|
|
match tag_chunk_id_u16 {
|
|
0x2ee1 => Some(
|
|
"High-16 chunk id 0x2ee1 matches the disassembly-grounded map-style bundle family already read by shell_setup_load_selected_profile_bundle_into_payload_record.",
|
|
),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn parse_runtime_post_span_probe(
|
|
bytes: &[u8],
|
|
runtime_trailer_block: Option<&SmpRuntimeTrailerBlock>,
|
|
) -> Option<SmpRuntimePostSpanProbe> {
|
|
let trailer = runtime_trailer_block?;
|
|
let span_target_offset = trailer.trailer_offset + trailer.length_high_u16 as usize;
|
|
let next_nonzero_offset = find_next_nonzero_offset(bytes, span_target_offset);
|
|
let header_candidates =
|
|
collect_runtime_post_span_header_candidates(bytes, span_target_offset, 0x8000);
|
|
let next_aligned_candidate_offset = header_candidates.first().map(|candidate| candidate.offset);
|
|
let next_aligned_candidate_words = header_candidates
|
|
.first()
|
|
.map(|candidate| candidate.words.clone())
|
|
.unwrap_or_default();
|
|
let grounded_progress_hits =
|
|
find_grounded_progress_high16_hits(bytes, span_target_offset, 0x8000);
|
|
|
|
Some(SmpRuntimePostSpanProbe {
|
|
profile_family: trailer.profile_family.clone(),
|
|
span_target_offset,
|
|
next_nonzero_offset,
|
|
next_aligned_candidate_offset,
|
|
next_aligned_candidate_hex_words: next_aligned_candidate_words
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect(),
|
|
next_aligned_candidate_words,
|
|
header_candidates,
|
|
grounded_progress_hits,
|
|
})
|
|
}
|
|
|
|
fn parse_classic_rehydrate_profile_probe(
|
|
bytes: &[u8],
|
|
runtime_post_span_probe: Option<&SmpRuntimePostSpanProbe>,
|
|
) -> Option<SmpClassicRehydrateProfileProbe> {
|
|
let post_span = runtime_post_span_probe?;
|
|
if post_span.profile_family != "rt3-classic-save-container-v1" {
|
|
return None;
|
|
}
|
|
|
|
let progress_32dc_offset =
|
|
parse_grounded_progress_hit_offset(&post_span.grounded_progress_hits, 0x32dc)?;
|
|
let progress_3714_offset =
|
|
parse_grounded_progress_hit_offset(&post_span.grounded_progress_hits, 0x3714)?;
|
|
let progress_3715_offset =
|
|
parse_grounded_progress_hit_offset(&post_span.grounded_progress_hits, 0x3715)?;
|
|
let packed_profile_offset = progress_3714_offset + 4;
|
|
let packed_profile_len = progress_3715_offset.checked_sub(packed_profile_offset)?;
|
|
if packed_profile_len != 0x108 {
|
|
return None;
|
|
}
|
|
|
|
let ascii_runs =
|
|
collect_ascii_previews_in_range(bytes, packed_profile_offset, progress_3715_offset, 4);
|
|
let packed_profile_block =
|
|
parse_classic_packed_profile_block(bytes, packed_profile_offset, packed_profile_len)?;
|
|
|
|
Some(SmpClassicRehydrateProfileProbe {
|
|
profile_family: post_span.profile_family.clone(),
|
|
progress_32dc_offset,
|
|
progress_3714_offset,
|
|
progress_3715_offset,
|
|
packed_profile_offset,
|
|
packed_profile_len,
|
|
packed_profile_len_hex: format!("0x{packed_profile_len:03x}"),
|
|
packed_profile_block,
|
|
ascii_runs,
|
|
})
|
|
}
|
|
|
|
fn parse_classic_packed_profile_block(
|
|
bytes: &[u8],
|
|
packed_profile_offset: usize,
|
|
packed_profile_len: usize,
|
|
) -> Option<SmpClassicPackedProfileBlock> {
|
|
let block_end = packed_profile_offset.checked_add(packed_profile_len)?;
|
|
if block_end > bytes.len() || packed_profile_len != 0x108 {
|
|
return None;
|
|
}
|
|
|
|
let leading_word_0 = read_u32_at(bytes, packed_profile_offset)?;
|
|
let trailing_zero_word_count_after_leading_word = (1..4)
|
|
.take_while(|index| {
|
|
read_u32_at(bytes, packed_profile_offset + (index * 4)).is_some_and(|word| word == 0)
|
|
})
|
|
.count();
|
|
let map_path_offset = 0x13;
|
|
let display_name_offset = 0x46;
|
|
let stable_nonzero_word_offsets = [0x00usize, 0x10, 0x78, 0x7c, 0x84, 0x88];
|
|
let stable_nonzero_words = stable_nonzero_word_offsets
|
|
.iter()
|
|
.filter_map(|relative_offset| {
|
|
let value = read_u32_at(bytes, packed_profile_offset + relative_offset)?;
|
|
if value == 0 {
|
|
return None;
|
|
}
|
|
|
|
Some(SmpPackedProfileWordLane {
|
|
relative_offset: *relative_offset,
|
|
relative_offset_hex: format!("0x{relative_offset:02x}"),
|
|
value,
|
|
value_hex: format!("0x{value:08x}"),
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
Some(SmpClassicPackedProfileBlock {
|
|
relative_len: packed_profile_len,
|
|
relative_len_hex: format!("0x{packed_profile_len:03x}"),
|
|
leading_word_0,
|
|
leading_word_0_hex: format!("0x{leading_word_0:08x}"),
|
|
trailing_zero_word_count_after_leading_word,
|
|
map_path_offset,
|
|
map_path: read_c_string_in_range(bytes, packed_profile_offset + map_path_offset, block_end),
|
|
display_name_offset,
|
|
display_name: read_c_string_in_range(
|
|
bytes,
|
|
packed_profile_offset + display_name_offset,
|
|
block_end,
|
|
),
|
|
profile_byte_0x77: bytes[packed_profile_offset + 0x77],
|
|
profile_byte_0x77_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0x77]),
|
|
profile_byte_0x82: bytes[packed_profile_offset + 0x82],
|
|
profile_byte_0x82_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0x82]),
|
|
profile_byte_0x97: bytes[packed_profile_offset + 0x97],
|
|
profile_byte_0x97_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0x97]),
|
|
profile_byte_0xc5: bytes[packed_profile_offset + 0xc5],
|
|
profile_byte_0xc5_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0xc5]),
|
|
stable_nonzero_words,
|
|
})
|
|
}
|
|
|
|
fn parse_rt3_105_packed_profile_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
header_variant_probe: Option<&SmpHeaderVariantProbe>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
) -> Option<SmpRt3105PackedProfileProbe> {
|
|
let profile_family = if container_profile.is_some_and(|profile| {
|
|
matches!(
|
|
profile.profile_family.as_str(),
|
|
"rt3-105-save-container-v1"
|
|
| "rt3-105-scenario-save-container-v1"
|
|
| "rt3-105-alt-save-container-v1"
|
|
)
|
|
}) {
|
|
container_profile
|
|
.expect("checked above")
|
|
.profile_family
|
|
.clone()
|
|
} else if file_extension_hint == Some("gms")
|
|
&& header_variant_probe.is_some_and(|probe| {
|
|
matches!(
|
|
probe.variant_family.as_str(),
|
|
"rt3-105-common-header-v1"
|
|
| "rt3-105-scenario-save-header-v1"
|
|
| "rt3-105-alt-save-header-v1"
|
|
| "rt3-map-header-family"
|
|
)
|
|
})
|
|
{
|
|
"rt3-105-save-analog-block-inferred".to_string()
|
|
} else {
|
|
return None;
|
|
};
|
|
|
|
if file_extension_hint != Some("gms") {
|
|
return None;
|
|
}
|
|
|
|
let map_path_offset = find_c_string_with_suffix_in_range(bytes, 0x7000, 0x9000, ".gmp")?;
|
|
let packed_profile_offset = map_path_offset.checked_sub(0x10)?;
|
|
let packed_profile_len = 0x108usize;
|
|
let block_end = packed_profile_offset.checked_add(packed_profile_len)?;
|
|
if block_end > bytes.len() {
|
|
return None;
|
|
}
|
|
|
|
let packed_profile_block =
|
|
parse_rt3_105_packed_profile_block(bytes, packed_profile_offset, packed_profile_len)?;
|
|
let ascii_runs = collect_ascii_previews_in_range(bytes, packed_profile_offset, block_end, 4);
|
|
|
|
Some(SmpRt3105PackedProfileProbe {
|
|
profile_family,
|
|
packed_profile_offset,
|
|
packed_profile_len,
|
|
packed_profile_len_hex: format!("0x{packed_profile_len:03x}"),
|
|
packed_profile_block,
|
|
ascii_runs,
|
|
})
|
|
}
|
|
|
|
fn parse_rt3_105_post_span_bridge_probe(
|
|
runtime_trailer_block: Option<&SmpRuntimeTrailerBlock>,
|
|
runtime_post_span_probe: Option<&SmpRuntimePostSpanProbe>,
|
|
rt3_105_packed_profile_probe: Option<&SmpRt3105PackedProfileProbe>,
|
|
) -> Option<SmpRt3105PostSpanBridgeProbe> {
|
|
let trailer = runtime_trailer_block?;
|
|
let post_span = runtime_post_span_probe?;
|
|
let packed_profile = rt3_105_packed_profile_probe?;
|
|
let supported = matches!(
|
|
trailer.profile_family.as_str(),
|
|
"rt3-105-save-container-v1"
|
|
| "rt3-105-scenario-save-container-v1"
|
|
| "rt3-105-alt-save-container-v1"
|
|
| "rt3-105-save-analog-block-inferred"
|
|
);
|
|
if !supported || trailer.profile_family != post_span.profile_family {
|
|
return None;
|
|
}
|
|
|
|
let next_candidate_high_u16_words = post_span
|
|
.header_candidates
|
|
.first()
|
|
.map(|candidate| candidate.high_u16_words.clone())
|
|
.unwrap_or_default();
|
|
let next_candidate_high_hex_words = next_candidate_high_u16_words
|
|
.iter()
|
|
.map(|word| format!("0x{word:04x}"))
|
|
.collect::<Vec<_>>();
|
|
let next_candidate_offset = post_span.next_aligned_candidate_offset;
|
|
let next_candidate_delta_from_span_target =
|
|
next_candidate_offset.and_then(|offset| offset.checked_sub(post_span.span_target_offset));
|
|
let packed_profile_delta_from_span_target = packed_profile
|
|
.packed_profile_offset
|
|
.checked_sub(post_span.span_target_offset)?;
|
|
let next_candidate_delta_from_packed_profile = next_candidate_offset
|
|
.map(|offset| offset as i64 - packed_profile.packed_profile_offset as i64);
|
|
|
|
let mut bridge_evidence = vec![
|
|
format!("profile family {}", trailer.profile_family),
|
|
format!("selector high {}", trailer.selector_high_hex),
|
|
format!("descriptor high {}", trailer.descriptor_high_hex),
|
|
format!(
|
|
"packed profile sits +0x{packed_profile_delta_from_span_target:x} from span target"
|
|
),
|
|
];
|
|
if let Some(delta) = next_candidate_delta_from_span_target {
|
|
bridge_evidence.push(format!("next candidate sits +0x{delta:x} from span target"));
|
|
}
|
|
if let Some(delta) = next_candidate_delta_from_packed_profile {
|
|
bridge_evidence.push(format!(
|
|
"next candidate is {delta:+#x} relative to packed profile"
|
|
));
|
|
}
|
|
|
|
let bridge_family = match (
|
|
trailer.selector_high_u16,
|
|
trailer.descriptor_high_u16,
|
|
next_candidate_high_u16_words.as_slice(),
|
|
) {
|
|
(0x7110, 0x7801 | 0x7401, [0x6200, 0x0000, 0xfff7, 0x5515, ..]) => {
|
|
bridge_evidence.push(format!(
|
|
"selector/descriptor pair 0x7110 -> 0x{:04x}",
|
|
trailer.descriptor_high_u16
|
|
));
|
|
bridge_evidence.push(
|
|
"next candidate begins with high-16 lanes 0x6200/0x0000/0xfff7/0x5515"
|
|
.to_string(),
|
|
);
|
|
"rt3-105-save-post-span-bridge-v1"
|
|
}
|
|
(0x54cd, 0x5901, [0x1500, 0x0100, 0x4100, 0x0200, ..]) => {
|
|
bridge_evidence.push("selector/descriptor pair 0x54cd -> 0x5901".to_string());
|
|
bridge_evidence.push(
|
|
"next candidate begins with high-16 lanes 0x1500/0x0100/0x4100/0x0200"
|
|
.to_string(),
|
|
);
|
|
"rt3-105-alt-save-post-span-bridge-v1"
|
|
}
|
|
(0x0001, 0x0186, [0x0186, 0x0006, 0x0006, 0x0001, ..]) => {
|
|
bridge_evidence.push("selector/descriptor pair 0x0001 -> 0x0186".to_string());
|
|
bridge_evidence.push(
|
|
"next candidate remains in the local cycle neighborhood with 0x0186/0x0006/0x0006/0x0001"
|
|
.to_string(),
|
|
);
|
|
"rt3-105-scenario-post-span-bridge-v1"
|
|
}
|
|
_ => "unknown",
|
|
}
|
|
.to_string();
|
|
|
|
Some(SmpRt3105PostSpanBridgeProbe {
|
|
profile_family: trailer.profile_family.clone(),
|
|
bridge_family,
|
|
bridge_evidence,
|
|
span_target_offset: post_span.span_target_offset,
|
|
next_candidate_offset,
|
|
next_candidate_delta_from_span_target,
|
|
packed_profile_offset: packed_profile.packed_profile_offset,
|
|
packed_profile_delta_from_span_target,
|
|
next_candidate_delta_from_packed_profile,
|
|
selector_high_u16: trailer.selector_high_u16,
|
|
selector_high_hex: trailer.selector_high_hex.clone(),
|
|
descriptor_high_u16: trailer.descriptor_high_u16,
|
|
descriptor_high_hex: trailer.descriptor_high_hex.clone(),
|
|
next_candidate_high_u16_words,
|
|
next_candidate_high_hex_words,
|
|
})
|
|
}
|
|
|
|
fn parse_rt3_105_save_bridge_payload_probe(
|
|
bytes: &[u8],
|
|
bridge_probe: Option<&SmpRt3105PostSpanBridgeProbe>,
|
|
) -> Option<SmpRt3105SaveBridgePayloadProbe> {
|
|
let bridge = bridge_probe?;
|
|
if bridge.bridge_family != "rt3-105-save-post-span-bridge-v1" {
|
|
return None;
|
|
}
|
|
|
|
let primary_block_offset = bridge.next_candidate_offset?;
|
|
let primary_block_word_count = 8usize;
|
|
let primary_words = read_u32_window(bytes, primary_block_offset, primary_block_word_count);
|
|
if primary_words.len() < primary_block_word_count {
|
|
return None;
|
|
}
|
|
|
|
let secondary_block_delta_from_primary = 0x1808usize;
|
|
let secondary_block_offset = primary_block_offset + secondary_block_delta_from_primary;
|
|
let secondary_block_end_offset = bridge.packed_profile_offset;
|
|
let secondary_block_len = secondary_block_end_offset.checked_sub(secondary_block_offset)?;
|
|
let secondary_preview_word_count = 32usize;
|
|
let secondary_words =
|
|
read_u32_window(bytes, secondary_block_offset, secondary_preview_word_count);
|
|
if secondary_words.len() < secondary_preview_word_count {
|
|
return None;
|
|
}
|
|
|
|
let primary_signature_matches = primary_words
|
|
== [
|
|
0x62000000, 0x00000000, 0xfff70000, 0x55150000, 0x55550000, 0x00000000, 0xfff70000,
|
|
0x54550000,
|
|
];
|
|
let secondary_prefix_matches = secondary_words.starts_with(&[
|
|
0x00050000, 0x00050005, 0xfff70000, 0x54540000, 0x545400f9, 0x00f900f9, 0x00f94008,
|
|
0x00001555,
|
|
]);
|
|
|
|
let mut evidence = vec![
|
|
"bridge family rt3-105-save-post-span-bridge-v1".to_string(),
|
|
format!("primary block offset 0x{primary_block_offset:08x}"),
|
|
format!("secondary block offset 0x{secondary_block_offset:08x}"),
|
|
format!("secondary block delta from primary 0x{secondary_block_delta_from_primary:x}"),
|
|
format!("secondary block end offset 0x{secondary_block_end_offset:08x}"),
|
|
format!("secondary block span 0x{secondary_block_len:x} bytes"),
|
|
];
|
|
if primary_signature_matches {
|
|
evidence.push(
|
|
"primary 8-word bridge block matches the observed 0x6200/0xfff7/0x5515/0x5555 spine"
|
|
.to_string(),
|
|
);
|
|
}
|
|
if secondary_prefix_matches {
|
|
evidence.push(
|
|
"secondary preview matches the observed 0x0005/0xfff7/0x5454 dense block prefix"
|
|
.to_string(),
|
|
);
|
|
}
|
|
|
|
Some(SmpRt3105SaveBridgePayloadProbe {
|
|
profile_family: bridge.profile_family.clone(),
|
|
bridge_family: bridge.bridge_family.clone(),
|
|
primary_block_offset,
|
|
primary_block_len: primary_block_word_count * 4,
|
|
primary_block_len_hex: format!("0x{:02x}", primary_block_word_count * 4),
|
|
primary_hex_words: primary_words
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect(),
|
|
primary_words,
|
|
secondary_block_offset,
|
|
secondary_block_delta_from_primary,
|
|
secondary_block_delta_from_primary_hex: format!("0x{secondary_block_delta_from_primary:x}"),
|
|
secondary_block_end_offset,
|
|
secondary_block_len,
|
|
secondary_block_len_hex: format!("0x{secondary_block_len:x}"),
|
|
secondary_preview_word_count,
|
|
secondary_hex_words: secondary_words
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect(),
|
|
secondary_words,
|
|
evidence,
|
|
})
|
|
}
|
|
|
|
fn parse_save_world_selection_context_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
) -> Option<SmpSaveWorldSelectionContextProbe> {
|
|
if file_extension_hint != Some("gms") {
|
|
return None;
|
|
}
|
|
let profile = container_profile?;
|
|
let supported = matches!(
|
|
profile.profile_family.as_str(),
|
|
"rt3-classic-save-container-v1"
|
|
| "rt3-105-save-container-v1"
|
|
| "rt3-105-scenario-save-container-v1"
|
|
| "rt3-105-alt-save-container-v1"
|
|
);
|
|
if !supported {
|
|
return None;
|
|
}
|
|
|
|
for chunk_tag_offset in find_u32_le_offsets(bytes, RT3_SAVE_WORLD_BLOCK_CHUNK_TAG) {
|
|
let payload_offset = chunk_tag_offset + 4;
|
|
let next_chunk_tag_offset = payload_offset.checked_add(RT3_SAVE_WORLD_BLOCK_LEN)?;
|
|
if read_u32_at(bytes, next_chunk_tag_offset) != Some(RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG) {
|
|
continue;
|
|
}
|
|
let selected_company_id_offset =
|
|
payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET;
|
|
let selected_chairman_profile_id_offset =
|
|
payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET;
|
|
let chairman_slot_selector_offset =
|
|
payload_offset + RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_SELECTOR_RELATIVE_OFFSET;
|
|
let campaign_override_flag_offset =
|
|
payload_offset + RT3_SAVE_WORLD_BLOCK_CAMPAIGN_OVERRIDE_FLAG_RELATIVE_OFFSET;
|
|
let chairman_role_gate_offset =
|
|
payload_offset + RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_RELATIVE_OFFSET;
|
|
let selected_company_id = read_u32_at(bytes, selected_company_id_offset)?;
|
|
let selected_chairman_profile_id = read_u32_at(bytes, selected_chairman_profile_id_offset)?;
|
|
let chairman_slot_selectors = bytes
|
|
.get(
|
|
chairman_slot_selector_offset
|
|
..chairman_slot_selector_offset + RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_COUNT,
|
|
)?
|
|
.to_vec();
|
|
let campaign_override_flag = *bytes.get(campaign_override_flag_offset)?;
|
|
let chairman_role_gate_bytes = (0..RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_COUNT)
|
|
.map(|slot_index| {
|
|
bytes
|
|
.get(
|
|
chairman_role_gate_offset
|
|
+ slot_index * RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_STRIDE,
|
|
)
|
|
.copied()
|
|
})
|
|
.collect::<Option<Vec<_>>>()?;
|
|
return Some(SmpSaveWorldSelectionContextProbe {
|
|
profile_family: profile.profile_family.clone(),
|
|
source_kind: "save-direct-world-block".to_string(),
|
|
semantic_family: "scenario-selected-company-and-chairman-context".to_string(),
|
|
chunk_tag_offset,
|
|
payload_offset,
|
|
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
|
|
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
|
|
selected_company_id_offset,
|
|
selected_company_id,
|
|
selected_company_id_hex: format!("0x{selected_company_id:08x}"),
|
|
selected_chairman_profile_id_offset,
|
|
selected_chairman_profile_id,
|
|
selected_chairman_profile_id_hex: format!("0x{selected_chairman_profile_id:08x}"),
|
|
chairman_slot_selector_offset,
|
|
chairman_slot_selectors,
|
|
campaign_override_flag_offset,
|
|
campaign_override_flag,
|
|
campaign_override_flag_hex: format!("0x{campaign_override_flag:02x}"),
|
|
chairman_role_gate_offset,
|
|
chairman_role_gate_bytes,
|
|
evidence: vec![
|
|
format!(
|
|
"chunk tag 0x32c8 at 0x{chunk_tag_offset:x} matches the fixed [world+0x04] save block"
|
|
),
|
|
format!(
|
|
"next chunk tag 0x32c9 appears at 0x{next_chunk_tag_offset:x}, matching the documented 0x4f2c payload span"
|
|
),
|
|
format!(
|
|
"selected company id comes from payload +0x{:x} ([world+0x21])",
|
|
RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET
|
|
),
|
|
format!(
|
|
"selected chairman profile id comes from payload +0x{:x} ([world+0x25])",
|
|
RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET
|
|
),
|
|
format!(
|
|
"16 chairman slot selector bytes come from payload +0x{:x} ([world+0x87])",
|
|
RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_SELECTOR_RELATIVE_OFFSET
|
|
),
|
|
format!(
|
|
"campaign override flag comes from payload +0x{:x} ([world+0xc5])",
|
|
RT3_SAVE_WORLD_BLOCK_CAMPAIGN_OVERRIDE_FLAG_RELATIVE_OFFSET
|
|
),
|
|
format!(
|
|
"chairman role-gate bytes come from payload +0x{:x} + slot*0x{:x} ([world+0x0bc3+slot*9])",
|
|
RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_RELATIVE_OFFSET,
|
|
RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_STRIDE
|
|
),
|
|
],
|
|
});
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn parse_save_world_issue_37_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
) -> Option<SmpSaveWorldIssue37Probe> {
|
|
if file_extension_hint != Some("gms") {
|
|
return None;
|
|
}
|
|
let profile = container_profile?;
|
|
let supported = matches!(
|
|
profile.profile_family.as_str(),
|
|
"rt3-classic-save-container-v1"
|
|
| "rt3-105-save-container-v1"
|
|
| "rt3-105-scenario-save-container-v1"
|
|
| "rt3-105-alt-save-container-v1"
|
|
);
|
|
if !supported {
|
|
return None;
|
|
}
|
|
|
|
for chunk_tag_offset in find_u32_le_offsets(bytes, RT3_SAVE_WORLD_BLOCK_CHUNK_TAG) {
|
|
let payload_offset = chunk_tag_offset + 4;
|
|
let next_chunk_tag_offset = payload_offset.checked_add(RT3_SAVE_WORLD_BLOCK_LEN)?;
|
|
if read_u32_at(bytes, next_chunk_tag_offset) != Some(RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG) {
|
|
continue;
|
|
}
|
|
let issue_value_lane = build_save_dword_candidate(
|
|
bytes,
|
|
payload_offset,
|
|
"issue_0x37_value",
|
|
RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET,
|
|
)?;
|
|
let issue_37_raw_u8 = read_u8_at(
|
|
bytes,
|
|
payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET,
|
|
)?;
|
|
let issue_38_raw_u8 = read_u8_at(
|
|
bytes,
|
|
payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET + 1,
|
|
)?;
|
|
let issue_39_raw_u8 = read_u8_at(
|
|
bytes,
|
|
payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET + 2,
|
|
)?;
|
|
let issue_3a_raw_u8 = read_u8_at(
|
|
bytes,
|
|
payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET + 3,
|
|
)?;
|
|
let multiplier_lane = build_save_dword_candidate(
|
|
bytes,
|
|
payload_offset,
|
|
"issue_0x37_multiplier",
|
|
RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_MULTIPLIER_RELATIVE_OFFSET,
|
|
)?;
|
|
let issue_opinion_base_terms_raw_i32 = build_save_i32_term_strip(
|
|
bytes,
|
|
payload_offset,
|
|
RT3_SAVE_WORLD_BLOCK_ISSUE_OPINION_BASE_TERMS_OFFSET,
|
|
RT3_SAVE_WORLD_BLOCK_ISSUE_OPINION_TERM_COUNT,
|
|
)?;
|
|
return Some(SmpSaveWorldIssue37Probe {
|
|
profile_family: profile.profile_family.clone(),
|
|
source_kind: "save-direct-world-block".to_string(),
|
|
semantic_family: "scenario-save-world-issue-0x37".to_string(),
|
|
chunk_tag_offset,
|
|
payload_offset,
|
|
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
|
|
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
|
|
issue_37_raw_u8,
|
|
issue_37_raw_hex: format!("0x{issue_37_raw_u8:02x}"),
|
|
issue_38_raw_u8,
|
|
issue_38_raw_hex: format!("0x{issue_38_raw_u8:02x}"),
|
|
issue_39_raw_u8,
|
|
issue_39_raw_hex: format!("0x{issue_39_raw_u8:02x}"),
|
|
issue_3a_raw_u8,
|
|
issue_3a_raw_hex: format!("0x{issue_3a_raw_u8:02x}"),
|
|
issue_value_lane,
|
|
multiplier_lane,
|
|
issue_opinion_base_terms_raw_i32,
|
|
evidence: vec![
|
|
format!(
|
|
"chunk tag 0x32c8 at 0x{chunk_tag_offset:x} matches the fixed [world+0x04] save block"
|
|
),
|
|
format!(
|
|
"next chunk tag 0x32c9 appears at 0x{next_chunk_tag_offset:x}, matching the documented 0x4f2c payload span"
|
|
),
|
|
format!(
|
|
"issue value lane uses payload +0x{:x} ([world+0x2d]); atlas notes tie 0x004339b0 to the clamped 0..4 issue-0x37 setter there",
|
|
RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET
|
|
),
|
|
format!(
|
|
"multiplier lane uses payload +0x{:x} ([world+0x29]); atlas notes tie 0x004339b0 to one companion scalar at that lane before company share-price refresh",
|
|
RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_MULTIPLIER_RELATIVE_OFFSET
|
|
),
|
|
format!(
|
|
"the adjacent byte strip at payload +0x{:x}..+0x{:x} carries raw issue slots 0x37..0x3a as {:02x} {:02x} {:02x} {:02x}",
|
|
RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET,
|
|
RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET + 3,
|
|
issue_37_raw_u8,
|
|
issue_38_raw_u8,
|
|
issue_39_raw_u8,
|
|
issue_3a_raw_u8
|
|
),
|
|
],
|
|
});
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn parse_save_world_finance_neighborhood_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
) -> Option<SmpSaveWorldFinanceNeighborhoodProbe> {
|
|
if file_extension_hint != Some("gms") {
|
|
return None;
|
|
}
|
|
let profile = container_profile?;
|
|
let supported = matches!(
|
|
profile.profile_family.as_str(),
|
|
"rt3-classic-save-container-v1"
|
|
| "rt3-105-save-container-v1"
|
|
| "rt3-105-scenario-save-container-v1"
|
|
| "rt3-105-alt-save-container-v1"
|
|
);
|
|
if !supported {
|
|
return None;
|
|
}
|
|
|
|
for chunk_tag_offset in find_u32_le_offsets(bytes, RT3_SAVE_WORLD_BLOCK_CHUNK_TAG) {
|
|
let payload_offset = chunk_tag_offset + 4;
|
|
let next_chunk_tag_offset = payload_offset.checked_add(RT3_SAVE_WORLD_BLOCK_LEN)?;
|
|
if read_u32_at(bytes, next_chunk_tag_offset) != Some(RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG) {
|
|
continue;
|
|
}
|
|
|
|
let current_calendar_tuple_word_lane = build_save_dword_candidate(
|
|
bytes,
|
|
payload_offset,
|
|
"current_calendar_tuple_word",
|
|
RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET,
|
|
)?;
|
|
let packed_year_word_raw_u16 = read_u16_at(
|
|
bytes,
|
|
payload_offset + RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET,
|
|
)?;
|
|
let partial_year_progress_raw_u8 = read_u8_at(
|
|
bytes,
|
|
payload_offset + RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET + 2,
|
|
)?;
|
|
let current_calendar_tuple_word_2_lane = build_save_dword_candidate(
|
|
bytes,
|
|
payload_offset,
|
|
"current_calendar_tuple_word_2",
|
|
RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_2_RELATIVE_OFFSET,
|
|
)?;
|
|
let absolute_counter_lane = build_save_dword_candidate(
|
|
bytes,
|
|
payload_offset,
|
|
"absolute_calendar_counter",
|
|
RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_RELATIVE_OFFSET,
|
|
)?;
|
|
let absolute_counter_mirror_lane = build_save_dword_candidate(
|
|
bytes,
|
|
payload_offset,
|
|
"absolute_calendar_counter_mirror",
|
|
RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_MIRROR_RELATIVE_OFFSET,
|
|
)?;
|
|
let stock_policy_raw_u8 = read_u8_at(
|
|
bytes,
|
|
payload_offset + RT3_SAVE_WORLD_BLOCK_STOCK_POLICY_RELATIVE_OFFSET,
|
|
)?;
|
|
let bond_policy_raw_u8 = read_u8_at(
|
|
bytes,
|
|
payload_offset + RT3_SAVE_WORLD_BLOCK_BOND_POLICY_RELATIVE_OFFSET,
|
|
)?;
|
|
let bankruptcy_policy_raw_u8 = read_u8_at(
|
|
bytes,
|
|
payload_offset + RT3_SAVE_WORLD_BLOCK_BANKRUPTCY_POLICY_RELATIVE_OFFSET,
|
|
)?;
|
|
let dividend_policy_raw_u8 = read_u8_at(
|
|
bytes,
|
|
payload_offset + RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET,
|
|
)?;
|
|
let building_density_growth_setting_lane = build_save_dword_candidate(
|
|
bytes,
|
|
payload_offset,
|
|
"building_density_growth_setting",
|
|
RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET,
|
|
)?;
|
|
let dword_candidates =
|
|
build_save_world_finance_neighborhood_candidates(bytes, payload_offset)?;
|
|
|
|
return Some(SmpSaveWorldFinanceNeighborhoodProbe {
|
|
profile_family: profile.profile_family.clone(),
|
|
source_kind: "save-direct-world-block".to_string(),
|
|
semantic_family: "scenario-save-world-finance-neighborhood".to_string(),
|
|
chunk_tag_offset,
|
|
payload_offset,
|
|
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
|
|
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
|
|
packed_year_word_raw_u16,
|
|
packed_year_word_raw_hex: format!("0x{packed_year_word_raw_u16:04x}"),
|
|
partial_year_progress_raw_u8,
|
|
partial_year_progress_raw_hex: format!("0x{partial_year_progress_raw_u8:02x}"),
|
|
current_calendar_tuple_word_lane,
|
|
current_calendar_tuple_word_2_lane,
|
|
absolute_counter_lane,
|
|
absolute_counter_mirror_lane,
|
|
stock_policy_raw_u8,
|
|
stock_policy_raw_hex: format!("0x{stock_policy_raw_u8:02x}"),
|
|
bond_policy_raw_u8,
|
|
bond_policy_raw_hex: format!("0x{bond_policy_raw_u8:02x}"),
|
|
bankruptcy_policy_raw_u8,
|
|
bankruptcy_policy_raw_hex: format!("0x{bankruptcy_policy_raw_u8:02x}"),
|
|
dividend_policy_raw_u8,
|
|
dividend_policy_raw_hex: format!("0x{dividend_policy_raw_u8:02x}"),
|
|
building_density_growth_setting_lane,
|
|
dword_candidates,
|
|
evidence: vec![
|
|
format!(
|
|
"chunk tag 0x32c8 at 0x{chunk_tag_offset:x} matches the fixed [world+0x04] save block"
|
|
),
|
|
format!(
|
|
"next chunk tag 0x32c9 appears at 0x{next_chunk_tag_offset:x}, matching the documented 0x4f2c payload span"
|
|
),
|
|
format!(
|
|
"payload +0x{:x}/+0x{:x}/+0x{:x} carry the saved world calendar tuple and absolute counter lanes that later company stock-issue cooldown readers compare against",
|
|
RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_RELATIVE_OFFSET,
|
|
RT3_SAVE_WORLD_BLOCK_CURRENT_CALENDAR_TUPLE_WORD_2_RELATIVE_OFFSET,
|
|
RT3_SAVE_WORLD_BLOCK_ABSOLUTE_COUNTER_RELATIVE_OFFSET
|
|
),
|
|
format!(
|
|
"payload +0x{:x}/+0x{:x}/+0x{:x}/+0x{:x} carry the stock, bond, bankruptcy, and dividend finance-policy bytes mirrored from scenario offsets 0x4a87/0x4a8b/0x4a8f/0x4a93",
|
|
RT3_SAVE_WORLD_BLOCK_STOCK_POLICY_RELATIVE_OFFSET,
|
|
RT3_SAVE_WORLD_BLOCK_BOND_POLICY_RELATIVE_OFFSET,
|
|
RT3_SAVE_WORLD_BLOCK_BANKRUPTCY_POLICY_RELATIVE_OFFSET,
|
|
RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET
|
|
),
|
|
format!(
|
|
"payload +0x{:x} carries the fixed-world building-density growth setting mirrored from `[world+0x4c7c]`, which the annual repurchase and dividend policy helpers both read directly",
|
|
RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET
|
|
),
|
|
"finance-neighborhood candidates cover the fixed dword strip around the grounded world calendar tuple, absolute-counter, selection-context, and issue-0x37 lanes so broader finance reader closure can build on one rehosted owner surface.".to_string(),
|
|
],
|
|
});
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn build_save_world_finance_neighborhood_candidates(
|
|
bytes: &[u8],
|
|
payload_offset: usize,
|
|
) -> Option<Vec<SmpSaveDwordCandidate>> {
|
|
(0..RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_WINDOW_LEN_DWORDS)
|
|
.map(|index| {
|
|
let relative_offset =
|
|
RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_ROOT_RELATIVE_OFFSET + index * 4;
|
|
let label = RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_CANDIDATE_FIELDS
|
|
.iter()
|
|
.find(|(_, named_offset)| *named_offset == relative_offset)
|
|
.map(|(name, _)| (*name).to_string())
|
|
.unwrap_or_else(|| format!("finance_neighborhood_word_{:02}", index + 1));
|
|
build_save_dword_candidate(bytes, payload_offset, &label, relative_offset)
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn parse_save_world_economic_tuning_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
) -> Option<SmpSaveWorldEconomicTuningProbe> {
|
|
if file_extension_hint != Some("gms") {
|
|
return None;
|
|
}
|
|
let profile = container_profile?;
|
|
let supported = matches!(
|
|
profile.profile_family.as_str(),
|
|
"rt3-classic-save-container-v1"
|
|
| "rt3-105-save-container-v1"
|
|
| "rt3-105-scenario-save-container-v1"
|
|
| "rt3-105-alt-save-container-v1"
|
|
);
|
|
if !supported {
|
|
return None;
|
|
}
|
|
|
|
for chunk_tag_offset in find_u32_le_offsets(bytes, RT3_SAVE_WORLD_BLOCK_CHUNK_TAG) {
|
|
let payload_offset = chunk_tag_offset + 4;
|
|
let next_chunk_tag_offset = payload_offset.checked_add(RT3_SAVE_WORLD_BLOCK_LEN)?;
|
|
if read_u32_at(bytes, next_chunk_tag_offset) != Some(RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG) {
|
|
continue;
|
|
}
|
|
let mirror_lane = build_save_dword_candidate(
|
|
bytes,
|
|
payload_offset,
|
|
"economic_tuning_mirror_lane_0",
|
|
RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET,
|
|
)?;
|
|
let tuning_lanes = RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_PRIMARY_RELATIVE_OFFSETS
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(lane_index, relative_offset)| {
|
|
build_save_dword_candidate(
|
|
bytes,
|
|
payload_offset,
|
|
&format!("economic_tuning_lane_{lane_index}"),
|
|
*relative_offset,
|
|
)
|
|
})
|
|
.collect::<Option<Vec<_>>>()?;
|
|
return Some(SmpSaveWorldEconomicTuningProbe {
|
|
profile_family: profile.profile_family.clone(),
|
|
source_kind: "save-direct-world-block".to_string(),
|
|
semantic_family: "scenario-save-world-economic-tuning".to_string(),
|
|
chunk_tag_offset,
|
|
payload_offset,
|
|
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
|
|
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
|
|
mirror_lane,
|
|
tuning_lanes,
|
|
evidence: vec![
|
|
format!(
|
|
"chunk tag 0x32c8 at 0x{chunk_tag_offset:x} matches the fixed [world+0x04] save block"
|
|
),
|
|
format!(
|
|
"next chunk tag 0x32c9 appears at 0x{next_chunk_tag_offset:x}, matching the documented 0x4f2c payload span"
|
|
),
|
|
format!(
|
|
"mirror lane uses payload +0x{:x} ([world+0x0bde])",
|
|
RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET
|
|
),
|
|
format!(
|
|
"primary tuning lanes use payload offsets {} matching the documented [world+0x0be2..+0x0bf6] float block",
|
|
RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_PRIMARY_RELATIVE_OFFSETS
|
|
.iter()
|
|
.map(|offset| format!("0x{offset:x}"))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
),
|
|
"Current atlas evidence keeps this fixed six-float world tuning band separate from the issue-0x37 investor-confidence lane."
|
|
.to_string(),
|
|
],
|
|
});
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn parse_save_company_collection_header_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
|
|
parse_save_tagged_collection_header_probe(
|
|
bytes,
|
|
file_extension_hint,
|
|
container_profile,
|
|
0x000061a9,
|
|
0x000061aa,
|
|
0x000061ab,
|
|
"save-company-tagged-header-counts",
|
|
"scenario-save-company-header-counts",
|
|
|header| {
|
|
header.direct_collection_flag == 1
|
|
&& header.live_id_bound >= 1
|
|
&& header.live_id_bound <= 0x20
|
|
&& header.live_record_count <= header.live_id_bound
|
|
&& header.direct_record_stride >= 0x1000
|
|
},
|
|
vec![
|
|
"save-side company collection uses tagged header family 0x61a9/0x61aa/0x61ab".to_string(),
|
|
"package-save per-company callback is currently grounded as a no-op stub, so this probe only claims header-level collection counts, not per-company payload".to_string(),
|
|
],
|
|
)
|
|
}
|
|
|
|
fn parse_save_chairman_profile_collection_header_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
|
|
parse_save_tagged_collection_header_probe(
|
|
bytes,
|
|
file_extension_hint,
|
|
container_profile,
|
|
0x00005209,
|
|
0x0000520a,
|
|
0x0000520b,
|
|
"save-chairman-profile-tagged-header-counts",
|
|
"scenario-save-chairman-profile-header-counts",
|
|
|header| {
|
|
header.direct_collection_flag == 1
|
|
&& header.live_id_bound >= 1
|
|
&& header.live_id_bound <= 0x20
|
|
&& header.live_record_count <= header.live_id_bound
|
|
&& header.direct_record_stride >= 0x800
|
|
&& header.direct_record_stride <= 0x2000
|
|
},
|
|
vec![
|
|
"save-side chairman/profile collection uses tagged header family 0x5209/0x520a/0x520b".to_string(),
|
|
"the direct-record chairman/profile family is the large-stride tagged collection with embedded name and biography payload, not the smaller train-side 0x5209 family".to_string(),
|
|
],
|
|
)
|
|
}
|
|
|
|
fn parse_save_train_collection_header_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
|
|
parse_save_tagged_collection_header_probe(
|
|
bytes,
|
|
file_extension_hint,
|
|
container_profile,
|
|
0x00005209,
|
|
0x0000520a,
|
|
0x0000520b,
|
|
"save-train-tagged-header-counts",
|
|
"scenario-save-train-header-counts",
|
|
|header| {
|
|
header.direct_collection_flag == 1
|
|
&& header.direct_record_stride >= 0x100
|
|
&& header.direct_record_stride <= 0x400
|
|
&& header.live_id_bound >= 0x10
|
|
&& header.live_id_bound <= 0x100
|
|
&& header.live_record_count >= 1
|
|
&& header.live_record_count <= header.live_id_bound
|
|
},
|
|
vec![
|
|
"save-side live train collection shares tagged header family 0x5209/0x520a/0x520b with other indexed direct-record bundles".to_string(),
|
|
"the grounded train-side candidate is the smaller direct-record family with stride 0x1d5 whose metadata payload carries Train N labels, distinct from the larger chairman/profile family and the non-direct region family".to_string(),
|
|
],
|
|
)
|
|
}
|
|
|
|
fn parse_save_train_collection_directory_probe(
|
|
bytes: &[u8],
|
|
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
|
) -> Option<SmpSaveTrainCollectionDirectoryProbe> {
|
|
let header_probe = header_probe?;
|
|
if header_probe.source_kind != "save-train-tagged-header-counts" {
|
|
return None;
|
|
}
|
|
let metadata_payload =
|
|
bytes.get(header_probe.metadata_tag_offset + 4..header_probe.records_tag_offset)?;
|
|
let directory_root_byte_offset =
|
|
SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX.checked_mul(4)?;
|
|
let live_record_count = header_probe.live_record_count as usize;
|
|
let directory_len_dwords =
|
|
live_record_count.checked_mul(SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT)?;
|
|
let directory_len_bytes = directory_len_dwords.checked_mul(4)?;
|
|
let directory_bytes = metadata_payload.get(
|
|
directory_root_byte_offset..directory_root_byte_offset.checked_add(directory_len_bytes)?,
|
|
)?;
|
|
let mut entries = Vec::with_capacity(live_record_count);
|
|
for index in 0..live_record_count {
|
|
let entry_offset =
|
|
index.checked_mul(SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT * 4)?;
|
|
let payload_relative_offset = read_u32_at(directory_bytes, entry_offset)?;
|
|
let previous_live_entry_id = read_u32_at(directory_bytes, entry_offset + 4)?;
|
|
let next_live_entry_id = read_u32_at(directory_bytes, entry_offset + 8)?;
|
|
entries.push(SmpSaveTrainCollectionDirectoryEntryProbe {
|
|
live_entry_id: (index + 1) as u32,
|
|
payload_relative_offset,
|
|
payload_relative_offset_hex: format!("0x{payload_relative_offset:08x}"),
|
|
payload_absolute_offset: header_probe
|
|
.metadata_tag_offset
|
|
.checked_add(4)?
|
|
.checked_add(payload_relative_offset as usize)?,
|
|
previous_live_entry_id,
|
|
previous_live_entry_id_hex: format!("0x{previous_live_entry_id:08x}"),
|
|
next_live_entry_id,
|
|
next_live_entry_id_hex: format!("0x{next_live_entry_id:08x}"),
|
|
});
|
|
}
|
|
let chain_head_live_entry_id = entries
|
|
.iter()
|
|
.find(|entry| entry.previous_live_entry_id == 0)
|
|
.map(|entry| entry.live_entry_id);
|
|
let chain_tail_live_entry_id = entries
|
|
.iter()
|
|
.find(|entry| entry.next_live_entry_id == 0)
|
|
.map(|entry| entry.live_entry_id);
|
|
let monotonic_offsets = entries
|
|
.windows(2)
|
|
.all(|window| window[0].payload_relative_offset < window[1].payload_relative_offset);
|
|
Some(SmpSaveTrainCollectionDirectoryProbe {
|
|
profile_family: header_probe.profile_family.clone(),
|
|
source_kind: "save-train-live-directory".to_string(),
|
|
semantic_family: "scenario-save-train-live-directory".to_string(),
|
|
metadata_tag_offset: header_probe.metadata_tag_offset,
|
|
records_tag_offset: header_probe.records_tag_offset,
|
|
close_tag_offset: header_probe.close_tag_offset,
|
|
directory_root_dword_index: SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX,
|
|
directory_entry_dword_count: SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT,
|
|
live_record_count: header_probe.live_record_count,
|
|
live_id_bound: header_probe.live_id_bound,
|
|
chain_head_live_entry_id,
|
|
chain_tail_live_entry_id,
|
|
entries,
|
|
evidence: vec![
|
|
"save-side train metadata payload exposes a live-entry directory immediately after the first 16 dwords, with payload-relative offsets pointing into the later records span".to_string(),
|
|
format!(
|
|
"train live directory decodes {} triplets of (payload_relative_offset, prev_live_entry_id, next_live_entry_id) from metadata dword index {}",
|
|
header_probe.live_record_count,
|
|
SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX
|
|
),
|
|
format!(
|
|
"decoded directory preserves a head/tail chain {:?}->{:?} and monotonic payload offsets={monotonic_offsets}",
|
|
chain_head_live_entry_id, chain_tail_live_entry_id
|
|
),
|
|
],
|
|
})
|
|
}
|
|
|
|
fn parse_save_region_collection_header_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
|
|
let probe = parse_save_tagged_collection_header_probe(
|
|
bytes,
|
|
file_extension_hint,
|
|
container_profile,
|
|
0x00005209,
|
|
0x0000520a,
|
|
0x0000520b,
|
|
"save-region-tagged-header-counts",
|
|
"scenario-save-region-header-counts",
|
|
|header| {
|
|
header.direct_collection_flag == 0
|
|
&& header.direct_record_stride == 0x06
|
|
&& header.live_id_bound >= 0x80
|
|
&& header.live_id_bound <= 0x200
|
|
&& header.live_record_count >= 0x80
|
|
&& header.live_record_count <= header.live_id_bound
|
|
},
|
|
vec![
|
|
"save-side live region collection shares tagged header family 0x5209/0x520a/0x520b with trains and chairman profiles, but uses the larger non-direct indexed family".to_string(),
|
|
"the grounded region-side candidate is the non-direct 0x5209 family with live_id_bound/count in the 0x96/0x91 range and Marker09-style default stems in the records span, distinct from the smaller direct train family".to_string(),
|
|
],
|
|
)?;
|
|
let records_preview = bytes
|
|
.get(probe.records_tag_offset + 4..probe.close_tag_offset)
|
|
.unwrap_or(&[]);
|
|
records_preview
|
|
.windows("Marker09".len())
|
|
.any(|window| window == b"Marker09")
|
|
.then_some(probe)
|
|
}
|
|
|
|
fn parse_save_region_record_triplet_probe(
|
|
bytes: &[u8],
|
|
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
|
) -> Option<SmpSaveRegionRecordTripletProbe> {
|
|
let header_probe = header_probe?;
|
|
if header_probe.source_kind != "save-region-tagged-header-counts" {
|
|
return None;
|
|
}
|
|
let records_payload =
|
|
bytes.get(header_probe.records_tag_offset + 4..header_probe.close_tag_offset)?;
|
|
let name_offsets = find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_NAME_TAG);
|
|
let policy_offsets = find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_POLICY_TAG);
|
|
let profile_offsets = find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_PROFILE_TAG);
|
|
let record_count = header_probe.live_record_count as usize;
|
|
if name_offsets.len() != record_count
|
|
|| policy_offsets.len() != record_count
|
|
|| profile_offsets.len() != record_count
|
|
{
|
|
return None;
|
|
}
|
|
let region_payload_start_offsets = bytes
|
|
.get(header_probe.metadata_tag_offset + 4..header_probe.records_tag_offset)
|
|
.and_then(|metadata_payload| {
|
|
let directory_root_byte_offset =
|
|
SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX.checked_mul(4)?;
|
|
let directory_len_dwords =
|
|
record_count.checked_mul(SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT)?;
|
|
let directory_len_bytes = directory_len_dwords.checked_mul(4)?;
|
|
let directory_bytes = metadata_payload.get(
|
|
directory_root_byte_offset
|
|
..directory_root_byte_offset.checked_add(directory_len_bytes)?,
|
|
)?;
|
|
let records_payload_absolute_offset = header_probe.records_tag_offset.checked_add(4)?;
|
|
(0..record_count)
|
|
.map(|index| {
|
|
let entry_offset = index
|
|
.checked_mul(SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT * 4)?;
|
|
let payload_relative_offset =
|
|
read_u32_at(directory_bytes, entry_offset)? as usize;
|
|
let payload_absolute_offset = header_probe
|
|
.metadata_tag_offset
|
|
.checked_add(4)?
|
|
.checked_add(payload_relative_offset)?;
|
|
let payload_start =
|
|
payload_absolute_offset.checked_sub(records_payload_absolute_offset)?;
|
|
(payload_start <= records_payload.len()).then_some(payload_start)
|
|
})
|
|
.collect::<Option<Vec<_>>>()
|
|
})
|
|
.unwrap_or_else(|| name_offsets.clone());
|
|
let mut entries = Vec::with_capacity(record_count);
|
|
for index in 0..record_count {
|
|
let record_payload_relative_offset = region_payload_start_offsets[index];
|
|
let name_tag_relative_offset = name_offsets[index];
|
|
let policy_tag_relative_offset = policy_offsets[index];
|
|
let profile_tag_relative_offset = profile_offsets[index];
|
|
let next_record_relative_offset = name_offsets
|
|
.get(index + 1)
|
|
.copied()
|
|
.unwrap_or(records_payload.len());
|
|
if record_payload_relative_offset > name_tag_relative_offset {
|
|
return None;
|
|
}
|
|
if !(name_tag_relative_offset < policy_tag_relative_offset
|
|
&& policy_tag_relative_offset < profile_tag_relative_offset
|
|
&& profile_tag_relative_offset < next_record_relative_offset)
|
|
{
|
|
return None;
|
|
}
|
|
let pre_name_prefix =
|
|
records_payload.get(record_payload_relative_offset..name_tag_relative_offset)?;
|
|
let pre_name_prefix_dword_candidates = build_region_record_prefix_dword_candidates(
|
|
record_payload_relative_offset,
|
|
pre_name_prefix,
|
|
);
|
|
let name_payload =
|
|
records_payload.get(name_tag_relative_offset + 4..policy_tag_relative_offset)?;
|
|
let name = parse_save_len_prefixed_ascii_name(name_payload)?;
|
|
let policy_chunk_len =
|
|
profile_tag_relative_offset.checked_sub(policy_tag_relative_offset + 4)?;
|
|
if policy_chunk_len != 0x1a {
|
|
return None;
|
|
}
|
|
let policy_payload =
|
|
records_payload.get(policy_tag_relative_offset + 4..profile_tag_relative_offset)?;
|
|
let policy_leading_f32_0 = f32::from_bits(read_u32_at(policy_payload, 0)?);
|
|
let policy_leading_f32_1 = f32::from_bits(read_u32_at(policy_payload, 4)?);
|
|
let policy_leading_f32_2 = f32::from_bits(read_u32_at(policy_payload, 8)?);
|
|
let mut policy_reserved_dwords = Vec::with_capacity(3);
|
|
let mut policy_reserved_dword_candidates = Vec::with_capacity(3);
|
|
for dword_index in 0..3 {
|
|
let reserved_relative_offset = 12 + dword_index * 4;
|
|
let raw_u32 = read_u32_at(policy_payload, reserved_relative_offset)?;
|
|
policy_reserved_dwords.push(raw_u32);
|
|
let relative_offset = record_payload_relative_offset
|
|
+ policy_tag_relative_offset
|
|
+ 4
|
|
+ reserved_relative_offset;
|
|
policy_reserved_dword_candidates.push(SmpSaveDwordCandidate {
|
|
label: format!("policy_reserved_word_{}", dword_index + 1),
|
|
relative_offset,
|
|
relative_offset_hex: format!("0x{relative_offset:x}"),
|
|
raw_u32,
|
|
raw_u32_hex: format!("0x{raw_u32:08x}"),
|
|
value_i32: raw_u32 as i32,
|
|
value_f32: f32::from_bits(raw_u32),
|
|
});
|
|
}
|
|
let policy_trailing_word = read_u16_at(policy_payload, 24)?;
|
|
let profile_chunk_len =
|
|
next_record_relative_offset.checked_sub(profile_tag_relative_offset + 4)?;
|
|
let profile_payload =
|
|
records_payload.get(profile_tag_relative_offset + 4..next_record_relative_offset)?;
|
|
let profile_collection = parse_save_region_profile_collection_probe(profile_payload);
|
|
entries.push(SmpSaveRegionRecordTripletEntryProbe {
|
|
record_index: index,
|
|
name,
|
|
record_payload_relative_offset,
|
|
record_payload_relative_offset_hex: format!("0x{record_payload_relative_offset:x}"),
|
|
name_tag_relative_offset,
|
|
policy_tag_relative_offset,
|
|
profile_tag_relative_offset,
|
|
pre_name_prefix_len: pre_name_prefix.len(),
|
|
pre_name_prefix_hex_bytes: pre_name_prefix
|
|
.iter()
|
|
.map(|byte| format!("0x{byte:02x}"))
|
|
.collect(),
|
|
pre_name_prefix_dword_candidates,
|
|
policy_chunk_len,
|
|
profile_chunk_len,
|
|
policy_leading_f32_0,
|
|
policy_leading_f32_1,
|
|
policy_leading_f32_2,
|
|
policy_reserved_dwords,
|
|
policy_reserved_dword_candidates,
|
|
policy_trailing_word,
|
|
policy_trailing_word_hex: format!("0x{policy_trailing_word:04x}"),
|
|
profile_collection,
|
|
});
|
|
}
|
|
let zero_trailing_padding_record_count = entries
|
|
.iter()
|
|
.filter(|entry| {
|
|
entry
|
|
.profile_collection
|
|
.as_ref()
|
|
.is_some_and(|collection| collection.trailing_padding_len == 0)
|
|
})
|
|
.count();
|
|
let records_with_nonzero_pre_name_prefix = entries
|
|
.iter()
|
|
.filter(|entry| entry.pre_name_prefix_len != 0)
|
|
.count();
|
|
let records_with_prefix_dword_candidates = entries
|
|
.iter()
|
|
.filter(|entry| !entry.pre_name_prefix_dword_candidates.is_empty())
|
|
.count();
|
|
let records_with_any_nonzero_policy_reserved_dword = entries
|
|
.iter()
|
|
.filter(|entry| {
|
|
entry
|
|
.policy_reserved_dwords
|
|
.iter()
|
|
.any(|raw_u32| *raw_u32 != 0)
|
|
})
|
|
.count();
|
|
let policy_reserved_nonzero_counts = (0..3)
|
|
.map(|dword_index| {
|
|
entries
|
|
.iter()
|
|
.filter(|entry| entry.policy_reserved_dwords[dword_index] != 0)
|
|
.count()
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let unique_nonzero_policy_reserved_triplets = entries
|
|
.iter()
|
|
.filter_map(|entry| {
|
|
let triplet = [
|
|
entry.policy_reserved_dwords[0],
|
|
entry.policy_reserved_dwords[1],
|
|
entry.policy_reserved_dwords[2],
|
|
];
|
|
triplet
|
|
.iter()
|
|
.any(|raw_u32| *raw_u32 != 0)
|
|
.then_some(triplet)
|
|
})
|
|
.collect::<BTreeSet<_>>()
|
|
.into_iter()
|
|
.collect::<Vec<_>>();
|
|
let unique_pre_name_prefix_lens = entries
|
|
.iter()
|
|
.map(|entry| entry.pre_name_prefix_len)
|
|
.collect::<BTreeSet<_>>()
|
|
.into_iter()
|
|
.collect::<Vec<_>>();
|
|
Some(SmpSaveRegionRecordTripletProbe {
|
|
profile_family: header_probe.profile_family.clone(),
|
|
source_kind: "save-region-record-triplets".to_string(),
|
|
semantic_family: "scenario-save-region-record-triplets".to_string(),
|
|
records_tag_offset: header_probe.records_tag_offset,
|
|
close_tag_offset: header_probe.close_tag_offset,
|
|
record_count,
|
|
entries,
|
|
evidence: vec![
|
|
"save-side region records in the non-direct Marker09 family are serialized as repeated 0x55f1/0x55f2/0x55f3 triplets inside the records span".to_string(),
|
|
format!(
|
|
"decoded {} region record triplets with one len-prefixed name chunk, one fixed policy chunk, and one trailing profile payload chunk per record",
|
|
record_count
|
|
),
|
|
"each fixed 0x55f2 policy chunk currently decodes as three leading f32 lanes, three reserved dwords, and one trailing u16 word".to_string(),
|
|
"the trailing 0x55f3 payload also carries an embedded direct profile collection with fixed 0x22-byte rows on grounded saves".to_string(),
|
|
format!(
|
|
"live-entry directory now also grounds the actual 0x520a payload starts: {} of {} records currently have nonzero bytes before the first 0x55f1 tag, with unique pre-name prefix lengths {:?}",
|
|
records_with_nonzero_pre_name_prefix,
|
|
record_count,
|
|
unique_pre_name_prefix_lens
|
|
),
|
|
format!(
|
|
"structured pre-name prefix dword candidates are currently present on {} of {} decoded region records",
|
|
records_with_prefix_dword_candidates,
|
|
record_count
|
|
),
|
|
format!(
|
|
"fixed 0x55f2 policy reserved dwords are nonzero on {} of {} decoded region records, with per-word nonzero counts {:?} and unique nonzero triplets {:?}",
|
|
records_with_any_nonzero_policy_reserved_dword,
|
|
record_count,
|
|
policy_reserved_nonzero_counts,
|
|
unique_nonzero_policy_reserved_triplets
|
|
),
|
|
format!(
|
|
"on grounded saves the 0x55f3 payload is fully consumed by that embedded profile collection: all {} decoded records currently have zero trailing padding beyond the direct profile rows",
|
|
zero_trailing_padding_record_count
|
|
),
|
|
],
|
|
})
|
|
}
|
|
|
|
fn build_region_record_prefix_dword_candidates(
|
|
record_payload_relative_offset: usize,
|
|
prefix_bytes: &[u8],
|
|
) -> Vec<SmpSaveDwordCandidate> {
|
|
prefix_bytes
|
|
.chunks_exact(4)
|
|
.enumerate()
|
|
.map(|(index, chunk)| {
|
|
let raw_u32 = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
|
|
let relative_offset = record_payload_relative_offset + index * 4;
|
|
SmpSaveDwordCandidate {
|
|
label: format!("pre_name_prefix_word_{}", index + 1),
|
|
relative_offset,
|
|
relative_offset_hex: format!("0x{relative_offset:x}"),
|
|
raw_u32,
|
|
raw_u32_hex: format!("0x{raw_u32:08x}"),
|
|
value_i32: raw_u32 as i32,
|
|
value_f32: f32::from_bits(raw_u32),
|
|
}
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn parse_save_region_queued_notice_record_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
region_header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
|
) -> Option<SmpSaveRegionQueuedNoticeRecordProbe> {
|
|
if file_extension_hint != Some("gms") {
|
|
return None;
|
|
}
|
|
let profile = container_profile?;
|
|
let max_region_id = region_header_probe
|
|
.map(|probe| probe.live_id_bound)
|
|
.unwrap_or(0x1000);
|
|
let entries = find_u32_le_offsets(bytes, SAVE_REGION_QUEUED_NOTICE_PAYLOAD_SEED)
|
|
.into_iter()
|
|
.filter_map(|payload_seed_offset| {
|
|
let node_base_offset = payload_seed_offset.checked_sub(4)?;
|
|
let _node_bytes = bytes
|
|
.get(node_base_offset..node_base_offset + SAVE_REGION_QUEUED_NOTICE_NODE_LEN)?;
|
|
let next_link_raw = read_u32_at(bytes, node_base_offset)?;
|
|
let kind = read_u32_at(bytes, node_base_offset + 8)?;
|
|
let promotion_latch_dword = read_u32_at(bytes, node_base_offset + 12)?;
|
|
let region_id = read_u32_at(bytes, node_base_offset + 16)?;
|
|
let amount = read_u32_at(bytes, node_base_offset + 20)?;
|
|
let trailing_sentinel_i32_0 = read_i32_at(bytes, node_base_offset + 24)?;
|
|
let trailing_sentinel_i32_1 = read_i32_at(bytes, node_base_offset + 28)?;
|
|
if !(kind == SAVE_REGION_QUEUED_NOTICE_NODE_KIND
|
|
&& promotion_latch_dword == 0
|
|
&& region_id >= 1
|
|
&& region_id <= max_region_id
|
|
&& amount > 0
|
|
&& trailing_sentinel_i32_0 == -1
|
|
&& trailing_sentinel_i32_1 == -1)
|
|
{
|
|
return None;
|
|
}
|
|
Some(SmpSaveRegionQueuedNoticeRecordEntryProbe {
|
|
node_base_offset,
|
|
payload_seed_offset,
|
|
next_link_raw,
|
|
next_link_raw_hex: format!("0x{next_link_raw:08x}"),
|
|
payload_seed_dword: SAVE_REGION_QUEUED_NOTICE_PAYLOAD_SEED,
|
|
payload_seed_dword_hex: format!("0x{SAVE_REGION_QUEUED_NOTICE_PAYLOAD_SEED:08x}"),
|
|
kind,
|
|
kind_hex: format!("0x{kind:08x}"),
|
|
promotion_latch_dword,
|
|
promotion_latch_dword_hex: format!("0x{promotion_latch_dword:08x}"),
|
|
region_id,
|
|
region_id_hex: format!("0x{region_id:08x}"),
|
|
amount,
|
|
amount_hex: format!("0x{amount:08x}"),
|
|
trailing_sentinel_i32_0,
|
|
trailing_sentinel_i32_0_hex: format!("0x{:08x}", trailing_sentinel_i32_0 as u32),
|
|
trailing_sentinel_i32_1,
|
|
trailing_sentinel_i32_1_hex: format!("0x{:08x}", trailing_sentinel_i32_1 as u32),
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
if entries.is_empty() {
|
|
return None;
|
|
}
|
|
Some(SmpSaveRegionQueuedNoticeRecordProbe {
|
|
profile_family: profile.profile_family.clone(),
|
|
source_kind: "save-region-queued-notice-records".to_string(),
|
|
semantic_family: "scenario-save-region-queued-notice-records".to_string(),
|
|
payload_seed_dword: SAVE_REGION_QUEUED_NOTICE_PAYLOAD_SEED,
|
|
payload_seed_dword_hex: format!("0x{SAVE_REGION_QUEUED_NOTICE_PAYLOAD_SEED:08x}"),
|
|
entries,
|
|
evidence: vec![
|
|
"save-side scan searches for the grounded region queued-notice payload seed 0x005c87a8 and validates the full 0x20-byte node shape from the atlas-backed queue owner".to_string(),
|
|
"accepted nodes require kind=7, promotion-latch dword=0, a bounded live region id, a positive amount, and trailing sentinel dwords -1/-1".to_string(),
|
|
],
|
|
})
|
|
}
|
|
|
|
fn parse_save_region_fixed_row_run_candidate_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
region_header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
|
) -> Option<SmpSaveRegionFixedRowRunCandidateProbe> {
|
|
if file_extension_hint != Some("gms") {
|
|
return None;
|
|
}
|
|
let profile = container_profile?;
|
|
let region_header_probe = region_header_probe?;
|
|
let target_row_count = region_header_probe.live_record_count as usize;
|
|
if target_row_count == 0 {
|
|
return None;
|
|
}
|
|
let scan_end_offset = region_header_probe.metadata_tag_offset;
|
|
let row_span_len = target_row_count.checked_mul(SAVE_REGION_FIXED_ROW_STRIDE)?;
|
|
let scan_bytes = bytes.get(..scan_end_offset)?;
|
|
let mut candidates = find_u32_le_offsets(scan_bytes, region_header_probe.live_record_count)
|
|
.into_iter()
|
|
.filter_map(|count_offset| {
|
|
let rows_offset = count_offset.checked_add(4)?;
|
|
let rows_end_offset = rows_offset.checked_add(row_span_len)?;
|
|
if rows_end_offset > scan_end_offset {
|
|
return None;
|
|
}
|
|
let rows_bytes = bytes.get(rows_offset..rows_end_offset)?;
|
|
let mut dword_lane_summaries =
|
|
Vec::with_capacity(SAVE_REGION_FIXED_ROW_DWORD_LANE_COUNT);
|
|
let mut best_probable_density_lane = None::<(usize, usize)>;
|
|
for lane_index in 0..SAVE_REGION_FIXED_ROW_DWORD_LANE_COUNT {
|
|
let relative_offset = lane_index * 4;
|
|
let mut zero_count = 0usize;
|
|
let mut nonzero_count = 0usize;
|
|
let mut probable_normal_f32_count = 0usize;
|
|
let mut small_unsigned_count = 0usize;
|
|
let mut distinct_values = BTreeSet::new();
|
|
let mut sample_values_hex = Vec::new();
|
|
for row_index in 0..target_row_count {
|
|
let row_offset = row_index * SAVE_REGION_FIXED_ROW_STRIDE + relative_offset;
|
|
let raw_u32 = read_u32_at(rows_bytes, row_offset)?;
|
|
if raw_u32 == 0 {
|
|
zero_count += 1;
|
|
} else {
|
|
nonzero_count += 1;
|
|
}
|
|
if probable_normal_f32_string(raw_u32).is_some() {
|
|
probable_normal_f32_count += 1;
|
|
}
|
|
if raw_u32 <= 1024 {
|
|
small_unsigned_count += 1;
|
|
}
|
|
if distinct_values.insert(raw_u32) && sample_values_hex.len() < 6 {
|
|
sample_values_hex.push(format!("0x{raw_u32:08x}"));
|
|
}
|
|
}
|
|
if best_probable_density_lane
|
|
.is_none_or(|(_, best_count)| probable_normal_f32_count > best_count)
|
|
{
|
|
best_probable_density_lane = Some((relative_offset, probable_normal_f32_count));
|
|
}
|
|
dword_lane_summaries.push(SmpSaveFixedRowRunDwordLaneSummary {
|
|
relative_offset,
|
|
relative_offset_hex: format!("0x{relative_offset:x}"),
|
|
zero_count,
|
|
nonzero_count,
|
|
distinct_value_count: distinct_values.len(),
|
|
probable_normal_f32_count,
|
|
small_unsigned_count,
|
|
sample_values_hex,
|
|
});
|
|
}
|
|
let mut trailing_values = BTreeSet::new();
|
|
let mut trailing_byte_zero_count = 0usize;
|
|
let mut trailing_byte_nonzero_count = 0usize;
|
|
let mut trailing_byte_sample_values_hex = Vec::new();
|
|
for row_index in 0..target_row_count {
|
|
let value = *rows_bytes.get(row_index * SAVE_REGION_FIXED_ROW_STRIDE + 0x28)?;
|
|
if value == 0 {
|
|
trailing_byte_zero_count += 1;
|
|
} else {
|
|
trailing_byte_nonzero_count += 1;
|
|
}
|
|
if trailing_values.insert(value) && trailing_byte_sample_values_hex.len() < 8 {
|
|
trailing_byte_sample_values_hex.push(format!("0x{value:02x}"));
|
|
}
|
|
}
|
|
let shape_signature = build_save_region_fixed_row_run_candidate_shape_signature(
|
|
&dword_lane_summaries,
|
|
trailing_byte_zero_count,
|
|
trailing_values.len(),
|
|
target_row_count,
|
|
);
|
|
let shape_family_signature =
|
|
build_save_region_fixed_row_run_candidate_shape_family_signature(
|
|
&dword_lane_summaries,
|
|
trailing_byte_zero_count,
|
|
trailing_values.len(),
|
|
target_row_count,
|
|
);
|
|
Some(SmpSaveRegionFixedRowRunCandidate {
|
|
count_offset,
|
|
count_offset_hex: format!("0x{count_offset:x}"),
|
|
row_count: target_row_count,
|
|
row_stride: SAVE_REGION_FIXED_ROW_STRIDE,
|
|
row_stride_hex: format!("0x{:x}", SAVE_REGION_FIXED_ROW_STRIDE),
|
|
rows_offset,
|
|
rows_offset_hex: format!("0x{rows_offset:x}"),
|
|
rows_end_offset,
|
|
rows_end_offset_hex: format!("0x{rows_end_offset:x}"),
|
|
distance_to_region_metadata_tag: scan_end_offset.saturating_sub(rows_end_offset),
|
|
distance_to_region_metadata_tag_hex: format!(
|
|
"0x{:x}",
|
|
scan_end_offset.saturating_sub(rows_end_offset)
|
|
),
|
|
dword_lane_summaries,
|
|
shape_signature,
|
|
shape_family_signature,
|
|
trailing_byte_zero_count,
|
|
trailing_byte_nonzero_count,
|
|
trailing_byte_distinct_value_count: trailing_values.len(),
|
|
trailing_byte_sample_values_hex,
|
|
best_probable_density_lane_relative_offset_hex: best_probable_density_lane
|
|
.filter(|(_, count)| *count != 0)
|
|
.map(|(relative_offset, _)| format!("0x{relative_offset:x}")),
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
candidates.sort_by_key(|candidate| {
|
|
(
|
|
Reverse(
|
|
candidate
|
|
.dword_lane_summaries
|
|
.iter()
|
|
.map(|summary| summary.probable_normal_f32_count)
|
|
.max()
|
|
.unwrap_or_default(),
|
|
),
|
|
candidate.distance_to_region_metadata_tag,
|
|
candidate.count_offset,
|
|
)
|
|
});
|
|
candidates.truncate(SAVE_REGION_FIXED_ROW_CANDIDATE_PROBE_LIMIT);
|
|
let candidate_count = candidates.len();
|
|
let best_candidate_offset_hex = candidates
|
|
.first()
|
|
.map(|candidate| candidate.rows_offset_hex.clone());
|
|
Some(SmpSaveRegionFixedRowRunCandidateProbe {
|
|
profile_family: profile.profile_family.clone(),
|
|
source_kind: "save-region-fixed-row-run-candidates".to_string(),
|
|
semantic_family: "scenario-save-region-fixed-row-run-candidates".to_string(),
|
|
target_row_count,
|
|
target_row_stride: SAVE_REGION_FIXED_ROW_STRIDE,
|
|
target_row_stride_hex: format!("0x{:x}", SAVE_REGION_FIXED_ROW_STRIDE),
|
|
scan_start_offset: 0,
|
|
scan_start_offset_hex: "0x0".to_string(),
|
|
scan_end_offset,
|
|
scan_end_offset_hex: format!("0x{scan_end_offset:x}"),
|
|
candidates,
|
|
evidence: vec![
|
|
format!(
|
|
"candidate scan looks for pre-region-header counted runs keyed to the grounded live region count {} with fixed row stride 0x{:x}",
|
|
target_row_count, SAVE_REGION_FIXED_ROW_STRIDE
|
|
),
|
|
format!(
|
|
"current scan range ends at region metadata tag offset 0x{:x}, because the atlas restore order places the fixed rows before the tagged 0x5209/0x520a/0x520b region collection",
|
|
scan_end_offset
|
|
),
|
|
format!(
|
|
"kept {} highest-signal candidates after sorting by probable-f32 lane density and proximity to the region metadata tag; best candidate rows offset is {:?}",
|
|
candidate_count, best_candidate_offset_hex
|
|
),
|
|
],
|
|
})
|
|
}
|
|
|
|
fn build_save_region_fixed_row_run_candidate_shape_signature(
|
|
dword_lane_summaries: &[SmpSaveFixedRowRunDwordLaneSummary],
|
|
trailing_byte_zero_count: usize,
|
|
trailing_byte_distinct_value_count: usize,
|
|
row_count: usize,
|
|
) -> String {
|
|
fn pick_lane_terms<F, P>(
|
|
summaries: &[SmpSaveFixedRowRunDwordLaneSummary],
|
|
score: F,
|
|
include: P,
|
|
row_count: usize,
|
|
max_terms: usize,
|
|
) -> Vec<String>
|
|
where
|
|
F: Fn(&SmpSaveFixedRowRunDwordLaneSummary) -> usize,
|
|
P: Fn(&SmpSaveFixedRowRunDwordLaneSummary) -> bool,
|
|
{
|
|
let high_signal_threshold = row_count.saturating_mul(3) / 4;
|
|
let mut picked = summaries
|
|
.iter()
|
|
.filter(|summary| include(summary))
|
|
.filter(|summary| score(summary) >= high_signal_threshold)
|
|
.map(|summary| {
|
|
(
|
|
summary.relative_offset,
|
|
format!("{}:{}", summary.relative_offset_hex, score(summary)),
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
if picked.is_empty() {
|
|
picked = summaries
|
|
.iter()
|
|
.filter(|summary| include(summary))
|
|
.filter_map(|summary| {
|
|
let value = score(summary);
|
|
(value != 0).then(|| {
|
|
(
|
|
summary.relative_offset,
|
|
format!("{}:{}", summary.relative_offset_hex, value),
|
|
)
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
picked.sort_by_key(|(relative_offset, term)| {
|
|
let value = term
|
|
.split(':')
|
|
.nth(1)
|
|
.and_then(|part| part.parse::<usize>().ok())
|
|
.unwrap_or_default();
|
|
(Reverse(value), *relative_offset)
|
|
});
|
|
picked.truncate(max_terms);
|
|
}
|
|
picked.into_iter().map(|(_, term)| term).collect()
|
|
}
|
|
|
|
let probable_terms = pick_lane_terms(
|
|
dword_lane_summaries,
|
|
|summary| summary.probable_normal_f32_count,
|
|
|_| true,
|
|
row_count,
|
|
3,
|
|
);
|
|
let small_terms = pick_lane_terms(
|
|
dword_lane_summaries,
|
|
|summary| summary.small_unsigned_count,
|
|
|summary| summary.nonzero_count != 0,
|
|
row_count,
|
|
2,
|
|
);
|
|
let zero_terms = pick_lane_terms(
|
|
dword_lane_summaries,
|
|
|summary| summary.zero_count,
|
|
|summary| summary.zero_count != row_count,
|
|
row_count,
|
|
2,
|
|
);
|
|
|
|
format!(
|
|
"pf32=[{}]|small=[{}]|zero=[{}]|trail={}/{}",
|
|
probable_terms.join(","),
|
|
small_terms.join(","),
|
|
zero_terms.join(","),
|
|
trailing_byte_zero_count,
|
|
trailing_byte_distinct_value_count
|
|
)
|
|
}
|
|
|
|
fn build_save_region_fixed_row_run_candidate_shape_family_signature(
|
|
dword_lane_summaries: &[SmpSaveFixedRowRunDwordLaneSummary],
|
|
trailing_byte_zero_count: usize,
|
|
trailing_byte_distinct_value_count: usize,
|
|
row_count: usize,
|
|
) -> String {
|
|
let dense_pf32_offsets = dword_lane_summaries
|
|
.iter()
|
|
.filter(|summary| summary.probable_normal_f32_count >= row_count.saturating_mul(3) / 4)
|
|
.map(|summary| summary.relative_offset_hex.clone())
|
|
.collect::<Vec<_>>();
|
|
let partial_zero_offsets = dword_lane_summaries
|
|
.iter()
|
|
.filter(|summary| {
|
|
summary.zero_count != 0
|
|
&& summary.zero_count != row_count
|
|
&& summary.zero_count >= row_count.saturating_mul(5) / 100
|
|
})
|
|
.map(|summary| summary.relative_offset_hex.clone())
|
|
.collect::<Vec<_>>();
|
|
let small_nonzero_offsets = dword_lane_summaries
|
|
.iter()
|
|
.filter(|summary| {
|
|
summary.nonzero_count != 0
|
|
&& summary.small_unsigned_count >= row_count.saturating_mul(8) / 100
|
|
})
|
|
.map(|summary| summary.relative_offset_hex.clone())
|
|
.collect::<Vec<_>>();
|
|
|
|
format!(
|
|
"dense_pf32=[{}]|small_nonzero=[{}]|partial_zero=[{}]|trail_bucket={}/{}",
|
|
dense_pf32_offsets.join(","),
|
|
small_nonzero_offsets.join(","),
|
|
partial_zero_offsets.join(","),
|
|
trailing_byte_zero_count / 8,
|
|
trailing_byte_distinct_value_count / 8
|
|
)
|
|
}
|
|
|
|
fn parse_save_placed_structure_record_triplet_probe(
|
|
bytes: &[u8],
|
|
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
|
) -> Option<SmpSavePlacedStructureRecordTripletProbe> {
|
|
let header_probe = header_probe?;
|
|
if header_probe.source_kind != "save-placed-structure-tagged-header-counts" {
|
|
return None;
|
|
}
|
|
let records_payload =
|
|
bytes.get(header_probe.records_tag_offset + 4..header_probe.close_tag_offset)?;
|
|
let name_offsets = find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_NAME_TAG);
|
|
let policy_offsets = find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_POLICY_TAG);
|
|
let profile_offsets = find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_PROFILE_TAG);
|
|
let record_count = header_probe.live_record_count as usize;
|
|
if name_offsets.len() != record_count
|
|
|| policy_offsets.len() != record_count
|
|
|| profile_offsets.len() != record_count
|
|
{
|
|
return None;
|
|
}
|
|
let mut entries = Vec::with_capacity(record_count);
|
|
for index in 0..record_count {
|
|
let name_tag_relative_offset = name_offsets[index];
|
|
let policy_tag_relative_offset = policy_offsets[index];
|
|
let profile_tag_relative_offset = profile_offsets[index];
|
|
let next_record_relative_offset = name_offsets
|
|
.get(index + 1)
|
|
.copied()
|
|
.unwrap_or(records_payload.len());
|
|
if !(name_tag_relative_offset < policy_tag_relative_offset
|
|
&& policy_tag_relative_offset < profile_tag_relative_offset
|
|
&& profile_tag_relative_offset < next_record_relative_offset)
|
|
{
|
|
return None;
|
|
}
|
|
let name_payload =
|
|
records_payload.get(name_tag_relative_offset + 4..policy_tag_relative_offset)?;
|
|
let (primary_name, secondary_name) = parse_save_len_prefixed_ascii_name_pair(name_payload)?;
|
|
let policy_chunk_len =
|
|
profile_tag_relative_offset.checked_sub(policy_tag_relative_offset + 4)?;
|
|
if policy_chunk_len != 0x1a {
|
|
return None;
|
|
}
|
|
let policy_payload =
|
|
records_payload.get(policy_tag_relative_offset + 4..profile_tag_relative_offset)?;
|
|
let policy_f32_lane_0 = f32::from_bits(read_u32_at(policy_payload, 0)?);
|
|
let policy_f32_lane_1 = f32::from_bits(read_u32_at(policy_payload, 4)?);
|
|
let policy_f32_lane_2 = f32::from_bits(read_u32_at(policy_payload, 8)?);
|
|
let policy_f32_lane_3 = f32::from_bits(read_u32_at(policy_payload, 12)?);
|
|
let policy_f32_lane_4 = f32::from_bits(read_u32_at(policy_payload, 16)?);
|
|
let policy_reserved_dword = read_u32_at(policy_payload, 20)?;
|
|
let policy_trailing_word = read_u16_at(policy_payload, 24)?;
|
|
let profile_chunk_len =
|
|
next_record_relative_offset.checked_sub(profile_tag_relative_offset + 4)?;
|
|
let profile_payload =
|
|
records_payload.get(profile_tag_relative_offset + 4..next_record_relative_offset)?;
|
|
let profile_open_marker = read_u32_at(profile_payload, 0)?;
|
|
if profile_open_marker != 0x00005dc1 {
|
|
return None;
|
|
}
|
|
let (profile_repeated_primary_name, profile_repeated_secondary_name) =
|
|
parse_save_len_prefixed_ascii_name_pair(profile_payload.get(4..)?)?;
|
|
let mut trailer_offset = 4usize;
|
|
let repeated_primary_len = *profile_payload.get(trailer_offset)? as usize;
|
|
trailer_offset += 1 + repeated_primary_len;
|
|
while matches!(profile_payload.get(trailer_offset), Some(0)) {
|
|
trailer_offset += 1;
|
|
}
|
|
let repeated_secondary_len = *profile_payload.get(trailer_offset)? as usize;
|
|
trailer_offset += 1 + repeated_secondary_len;
|
|
let mut matched_footer = None;
|
|
for candidate_offset in [trailer_offset, trailer_offset + 1] {
|
|
if let (
|
|
Some(profile_payload_dword),
|
|
Some(profile_sentinel_i32),
|
|
Some(profile_close_marker),
|
|
) = (
|
|
read_u32_at(profile_payload, candidate_offset),
|
|
read_i32_at(profile_payload, candidate_offset + 4),
|
|
read_u32_at(profile_payload, candidate_offset + 8),
|
|
) {
|
|
if profile_close_marker == 0x00005dc2 {
|
|
matched_footer = Some((
|
|
candidate_offset,
|
|
profile_payload_dword,
|
|
profile_sentinel_i32,
|
|
profile_close_marker,
|
|
));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
let (
|
|
profile_footer_relative_offset,
|
|
profile_payload_dword,
|
|
profile_sentinel_i32,
|
|
profile_close_marker,
|
|
) = matched_footer?;
|
|
let profile_pre_footer_padding = profile_payload
|
|
.get(trailer_offset..profile_footer_relative_offset)?
|
|
.iter()
|
|
.map(|byte| format!("0x{byte:02x}"))
|
|
.collect::<Vec<_>>();
|
|
let profile_companion_byte_u8 = if profile_pre_footer_padding.len() == 1 {
|
|
profile_payload.get(trailer_offset).copied()
|
|
} else {
|
|
None
|
|
};
|
|
let (profile_status_kind, farm_growth_stage_index) =
|
|
derive_save_placed_structure_profile_status(
|
|
&primary_name,
|
|
&secondary_name,
|
|
profile_sentinel_i32,
|
|
);
|
|
entries.push(SmpSavePlacedStructureRecordTripletEntryProbe {
|
|
record_index: index,
|
|
primary_name,
|
|
secondary_name,
|
|
name_tag_relative_offset,
|
|
policy_tag_relative_offset,
|
|
profile_tag_relative_offset,
|
|
policy_chunk_len,
|
|
profile_chunk_len,
|
|
policy_f32_lane_0,
|
|
policy_f32_lane_1,
|
|
policy_f32_lane_2,
|
|
policy_f32_lane_3,
|
|
policy_f32_lane_4,
|
|
policy_reserved_dword,
|
|
policy_trailing_word,
|
|
policy_trailing_word_hex: format!("0x{policy_trailing_word:04x}"),
|
|
profile_open_marker,
|
|
profile_open_marker_hex: format!("0x{profile_open_marker:08x}"),
|
|
profile_repeated_primary_name,
|
|
profile_repeated_secondary_name,
|
|
profile_footer_relative_offset,
|
|
profile_footer_relative_offset_hex: format!("0x{profile_footer_relative_offset:x}"),
|
|
profile_pre_footer_padding_len: profile_pre_footer_padding.len(),
|
|
profile_pre_footer_padding_hex_bytes: profile_pre_footer_padding,
|
|
profile_companion_byte_hex: profile_companion_byte_u8
|
|
.map(|byte| format!("0x{byte:02x}")),
|
|
profile_companion_byte_u8,
|
|
profile_payload_dword,
|
|
profile_payload_dword_hex: format!("0x{profile_payload_dword:08x}"),
|
|
profile_sentinel_i32,
|
|
profile_status_kind: profile_status_kind.to_string(),
|
|
farm_growth_stage_index,
|
|
profile_close_marker,
|
|
profile_close_marker_hex: format!("0x{profile_close_marker:08x}"),
|
|
});
|
|
}
|
|
let farm_growth_stage_entry_count = entries
|
|
.iter()
|
|
.filter(|entry| entry.farm_growth_stage_index.is_some())
|
|
.count();
|
|
Some(SmpSavePlacedStructureRecordTripletProbe {
|
|
profile_family: header_probe.profile_family.clone(),
|
|
source_kind: "save-placed-structure-record-triplets".to_string(),
|
|
semantic_family: "scenario-save-placed-structure-record-triplets".to_string(),
|
|
records_tag_offset: header_probe.records_tag_offset,
|
|
close_tag_offset: header_probe.close_tag_offset,
|
|
record_count,
|
|
entries,
|
|
evidence: vec![
|
|
"save-side placed-structure records are serialized as repeated 0x55f1/0x55f2/0x55f3 triplets inside the tagged records span".to_string(),
|
|
"the 0x55f1 chunk currently exposes two len-prefixed structure-name stems before the fixed 0x55f2 policy row".to_string(),
|
|
"each fixed placed-structure 0x55f2 policy chunk currently decodes as five f32-like lanes, one reserved dword, and one trailing u16 word".to_string(),
|
|
format!(
|
|
"the compact 0x55f3 footer status lane behaves like a farm growth-stage bucket on grounded saves: {farm_growth_stage_entry_count} entries expose nonnegative 0..11 values and all observed non-farm families stay at -1"
|
|
),
|
|
],
|
|
})
|
|
}
|
|
|
|
fn derive_save_placed_structure_profile_status(
|
|
primary_name: &str,
|
|
secondary_name: &str,
|
|
raw_status: i32,
|
|
) -> (&'static str, Option<u8>) {
|
|
let looks_like_farm = primary_name.starts_with("Farm") || secondary_name.contains("Farm");
|
|
if raw_status == -1 {
|
|
return ("unset", None);
|
|
}
|
|
if looks_like_farm && (0..=11).contains(&raw_status) {
|
|
return ("farm_growth_stage_bucket", Some(raw_status as u8));
|
|
}
|
|
("opaque_nondefault", None)
|
|
}
|
|
|
|
fn parse_save_placed_structure_collection_header_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
|
|
parse_save_tagged_collection_header_probe(
|
|
bytes,
|
|
file_extension_hint,
|
|
container_profile,
|
|
0x000036b1,
|
|
0x000036b2,
|
|
0x000036b3,
|
|
"save-placed-structure-tagged-header-counts",
|
|
"scenario-save-placed-structure-header-counts",
|
|
|header| {
|
|
header.direct_collection_flag == 0
|
|
&& header.direct_record_stride >= 1
|
|
&& header.direct_record_stride <= 0x20
|
|
&& header.live_id_bound >= 0x100
|
|
&& header.live_record_count >= 0x100
|
|
&& header.live_record_count <= header.live_id_bound
|
|
},
|
|
vec![
|
|
"save-side placed-structure collection uses tagged family 0x36b1/0x36b2/0x36b3 beneath the wider local-runtime and route-entry rebuild owners".to_string(),
|
|
"current evidence only grounds header-level placed-structure collection counts here; direct record-body reconstruction still needs the later per-entry load/save slot study.".to_string(),
|
|
],
|
|
)
|
|
}
|
|
|
|
fn parse_save_placed_structure_dynamic_side_buffer_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
) -> Option<SmpSavePlacedStructureDynamicSideBufferProbe> {
|
|
#[derive(Clone)]
|
|
struct EmbeddedNameRow {
|
|
name_tag_relative_offset: usize,
|
|
prefix_leading_dword: u32,
|
|
prefix_trailing_word: u16,
|
|
prefix_separator_byte: u8,
|
|
name_payload_relative_offset: usize,
|
|
name_payload_len: usize,
|
|
primary_name: Option<String>,
|
|
secondary_name: Option<String>,
|
|
tertiary_name: Option<String>,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct PrefixPatternAccumulator {
|
|
count: usize,
|
|
first_name_tag_relative_offset: usize,
|
|
first_primary_name: Option<String>,
|
|
first_secondary_name: Option<String>,
|
|
section_like_primary_name_count: usize,
|
|
cap_like_primary_name_count: usize,
|
|
other_primary_name_count: usize,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct NamePairAccumulator {
|
|
count: usize,
|
|
first_name_tag_relative_offset: usize,
|
|
prefix_counts: BTreeMap<(u32, u16, u8), usize>,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
struct PayloadEnvelopeRow {
|
|
name_tag_relative_offset: usize,
|
|
primary_name: Option<String>,
|
|
secondary_name: Option<String>,
|
|
name_payload_end_relative_offset: Option<usize>,
|
|
policy_tag_relative_offset: Option<usize>,
|
|
profile_tag_relative_offset: Option<usize>,
|
|
next_name_tag_relative_offset: Option<usize>,
|
|
name_to_policy_gap_len: Option<usize>,
|
|
policy_chunk_len: Option<usize>,
|
|
profile_chunk_len_to_next_name_or_end: Option<usize>,
|
|
short_profile_first_flag_byte: Option<u8>,
|
|
short_profile_second_flag_byte: Option<u8>,
|
|
}
|
|
|
|
if file_extension_hint != Some("gms") {
|
|
return None;
|
|
}
|
|
let profile = container_profile?;
|
|
if !matches!(
|
|
profile.profile_family.as_str(),
|
|
"rt3-classic-save-container-v1"
|
|
| "rt3-105-save-container-v1"
|
|
| "rt3-105-scenario-save-container-v1"
|
|
| "rt3-105-alt-save-container-v1"
|
|
) {
|
|
return None;
|
|
}
|
|
|
|
let metadata_offsets = find_u32_le_offsets(bytes, 0x000038a5);
|
|
let records_offsets = find_u32_le_offsets(bytes, 0x000038a6);
|
|
let close_offsets = find_u32_le_offsets(bytes, 0x000038a7);
|
|
for metadata_tag_offset in metadata_offsets {
|
|
let Some(records_tag_offset) = records_offsets
|
|
.iter()
|
|
.copied()
|
|
.find(|offset| *offset > metadata_tag_offset)
|
|
else {
|
|
continue;
|
|
};
|
|
let Some(close_tag_offset) = close_offsets
|
|
.iter()
|
|
.copied()
|
|
.find(|offset| *offset > records_tag_offset)
|
|
else {
|
|
continue;
|
|
};
|
|
let Some(payload) = bytes.get(metadata_tag_offset + 4..records_tag_offset) else {
|
|
continue;
|
|
};
|
|
if payload.len() < INDEXED_COLLECTION_SERIALIZED_HEADER_LEN {
|
|
continue;
|
|
}
|
|
let Some(header_words) = (0..INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT)
|
|
.map(|index| read_u32_at(payload, index * 4))
|
|
.collect::<Option<Vec<_>>>()
|
|
else {
|
|
continue;
|
|
};
|
|
let Some(header_words): Option<[u32; INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT]> =
|
|
header_words.try_into().ok()
|
|
else {
|
|
continue;
|
|
};
|
|
let summary = IndexedCollectionHeaderSummary {
|
|
metadata_tag_offset,
|
|
records_tag_offset,
|
|
close_tag_offset,
|
|
direct_collection_flag: header_words[0],
|
|
direct_record_stride: header_words[1],
|
|
live_id_bound: header_words[4],
|
|
live_record_count: header_words[5],
|
|
header_words,
|
|
};
|
|
if !(summary.direct_collection_flag == 0
|
|
&& summary.direct_record_stride == 0x06
|
|
&& summary.header_words.get(2) == Some(&1000)
|
|
&& summary.header_words.get(3) == Some(&500)
|
|
&& summary.header_words.get(6) == Some(&0)
|
|
&& summary.header_words.get(7) == Some(&1)
|
|
&& summary.live_id_bound >= 0x100
|
|
&& summary.live_id_bound <= 0x1000
|
|
&& summary.live_record_count >= 0x100
|
|
&& summary.live_record_count <= summary.live_id_bound)
|
|
{
|
|
continue;
|
|
}
|
|
let Some(records_payload) = bytes.get(records_tag_offset + 4..close_tag_offset) else {
|
|
continue;
|
|
};
|
|
let embedded_name_tag_offsets =
|
|
find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_NAME_TAG);
|
|
let Some(&first_embedded_name_tag_relative_offset) = embedded_name_tag_offsets.first()
|
|
else {
|
|
continue;
|
|
};
|
|
let Some(prefix_payload) = records_payload.get(..first_embedded_name_tag_relative_offset)
|
|
else {
|
|
continue;
|
|
};
|
|
if prefix_payload.len() < 7 {
|
|
continue;
|
|
}
|
|
let Some(owner_shared_dword) = read_u32_at(prefix_payload, 0) else {
|
|
continue;
|
|
};
|
|
let owner_shared_dword_relative_offset = 0usize;
|
|
let first_record_child_count_after_owner_shared = read_u16_at(records_payload, 4);
|
|
let first_record_saved_primary_child_byte_after_owner_shared =
|
|
read_u8_at(records_payload, 6);
|
|
let first_record_first_name_tag_relative_offset_after_owner_shared = (3usize..=16usize)
|
|
.find(|offset| {
|
|
read_u16_at(records_payload, 4 + *offset) == Some(SAVE_REGION_RECORD_NAME_TAG)
|
|
});
|
|
let prefix_leading_dword = owner_shared_dword;
|
|
let Some(prefix_trailing_word) = read_u16_at(prefix_payload, 4) else {
|
|
continue;
|
|
};
|
|
let Some(prefix_separator_byte) = prefix_payload.get(6).copied() else {
|
|
continue;
|
|
};
|
|
let mut parsed_embedded_names = None;
|
|
for relative_name_offset in [4usize, 6usize] {
|
|
let Some(name_payload) = records_payload
|
|
.get(first_embedded_name_tag_relative_offset + relative_name_offset..)
|
|
else {
|
|
continue;
|
|
};
|
|
if let Some(names) = parse_save_len_prefixed_ascii_name_triplet(name_payload) {
|
|
parsed_embedded_names = Some(names);
|
|
break;
|
|
}
|
|
}
|
|
let Some((
|
|
first_embedded_primary_name,
|
|
first_embedded_secondary_name,
|
|
first_embedded_tertiary_name,
|
|
)) = parsed_embedded_names
|
|
else {
|
|
continue;
|
|
};
|
|
let embedded_name_rows = embedded_name_tag_offsets
|
|
.iter()
|
|
.copied()
|
|
.filter_map(|name_tag_relative_offset| {
|
|
let prefix_payload = records_payload.get(..name_tag_relative_offset)?;
|
|
if prefix_payload.len() < 7 {
|
|
return None;
|
|
}
|
|
let prefix_leading_dword = read_u32_at(prefix_payload, prefix_payload.len() - 7)?;
|
|
let prefix_trailing_word = read_u16_at(prefix_payload, prefix_payload.len() - 3)?;
|
|
let prefix_separator_byte = *prefix_payload.last()?;
|
|
let mut parsed_names = None;
|
|
for relative_name_offset in [4usize, 6usize] {
|
|
let Some(name_payload) =
|
|
records_payload.get(name_tag_relative_offset + relative_name_offset..)
|
|
else {
|
|
continue;
|
|
};
|
|
if let Some((names, name_payload_len)) =
|
|
parse_save_len_prefixed_ascii_name_triplet_and_consumed_len(name_payload)
|
|
{
|
|
parsed_names = Some((relative_name_offset, name_payload_len, names));
|
|
break;
|
|
}
|
|
}
|
|
let (name_payload_relative_offset, name_payload_len, names) =
|
|
parsed_names.unwrap_or((4usize, 0usize, Default::default()));
|
|
let (primary_name, secondary_name, tertiary_name) = names;
|
|
Some(EmbeddedNameRow {
|
|
name_tag_relative_offset,
|
|
prefix_leading_dword,
|
|
prefix_trailing_word,
|
|
prefix_separator_byte,
|
|
name_payload_relative_offset,
|
|
name_payload_len,
|
|
primary_name: (!primary_name.is_empty()).then_some(primary_name),
|
|
secondary_name: (!secondary_name.is_empty()).then_some(secondary_name),
|
|
tertiary_name,
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let policy_tag_offsets =
|
|
find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_POLICY_TAG);
|
|
let profile_tag_offsets =
|
|
find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_PROFILE_TAG);
|
|
let payload_envelope_rows = embedded_name_rows
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(row_index, row)| {
|
|
let next_name_tag_relative_offset = embedded_name_tag_offsets
|
|
.get(row_index + 1)
|
|
.copied()
|
|
.or(Some(records_payload.len()));
|
|
let name_payload_end_relative_offset = Some(
|
|
row.name_tag_relative_offset
|
|
+ row.name_payload_relative_offset
|
|
+ row.name_payload_len,
|
|
);
|
|
let policy_tag_relative_offset =
|
|
policy_tag_offsets.iter().copied().find(|offset| {
|
|
*offset > row.name_tag_relative_offset
|
|
&& next_name_tag_relative_offset
|
|
.is_none_or(|next_name| *offset < next_name)
|
|
});
|
|
let profile_tag_relative_offset = policy_tag_relative_offset.and_then(|policy| {
|
|
profile_tag_offsets.iter().copied().find(|offset| {
|
|
*offset > policy
|
|
&& next_name_tag_relative_offset
|
|
.is_none_or(|next_name| *offset < next_name)
|
|
})
|
|
});
|
|
let name_to_policy_gap_len = name_payload_end_relative_offset
|
|
.zip(policy_tag_relative_offset)
|
|
.and_then(
|
|
|(name_payload_end_relative_offset, policy_tag_relative_offset)| {
|
|
policy_tag_relative_offset.checked_sub(name_payload_end_relative_offset)
|
|
},
|
|
);
|
|
let policy_chunk_len = policy_tag_relative_offset
|
|
.zip(profile_tag_relative_offset)
|
|
.and_then(
|
|
|(policy_tag_relative_offset, profile_tag_relative_offset)| {
|
|
profile_tag_relative_offset.checked_sub(policy_tag_relative_offset + 4)
|
|
},
|
|
);
|
|
let profile_chunk_len_to_next_name_or_end =
|
|
profile_tag_relative_offset.and_then(|profile_tag_relative_offset| {
|
|
next_name_tag_relative_offset.and_then(|next_name_tag_relative_offset| {
|
|
next_name_tag_relative_offset
|
|
.checked_sub(profile_tag_relative_offset + 4)
|
|
})
|
|
});
|
|
let short_profile_first_flag_byte =
|
|
profile_tag_relative_offset.and_then(|profile_tag_relative_offset| {
|
|
records_payload
|
|
.get(profile_tag_relative_offset + 4)
|
|
.copied()
|
|
});
|
|
let short_profile_second_flag_byte =
|
|
profile_tag_relative_offset.and_then(|profile_tag_relative_offset| {
|
|
records_payload
|
|
.get(profile_tag_relative_offset + 5)
|
|
.copied()
|
|
});
|
|
PayloadEnvelopeRow {
|
|
name_tag_relative_offset: row.name_tag_relative_offset,
|
|
primary_name: row.primary_name.clone(),
|
|
secondary_name: row.secondary_name.clone(),
|
|
name_payload_end_relative_offset,
|
|
policy_tag_relative_offset,
|
|
profile_tag_relative_offset,
|
|
next_name_tag_relative_offset,
|
|
name_to_policy_gap_len,
|
|
policy_chunk_len,
|
|
profile_chunk_len_to_next_name_or_end,
|
|
short_profile_first_flag_byte,
|
|
short_profile_second_flag_byte,
|
|
}
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let embedded_name_row_samples = embedded_name_rows
|
|
.iter()
|
|
.take(8)
|
|
.enumerate()
|
|
.map(
|
|
|(sample_index, row)| SmpSavePlacedStructureDynamicSideBufferSampleEntry {
|
|
sample_index,
|
|
name_tag_relative_offset: row.name_tag_relative_offset,
|
|
prefix_leading_dword: row.prefix_leading_dword,
|
|
prefix_leading_dword_hex: format!("0x{:08x}", row.prefix_leading_dword),
|
|
prefix_trailing_word: row.prefix_trailing_word,
|
|
prefix_trailing_word_hex: format!("0x{:04x}", row.prefix_trailing_word),
|
|
prefix_separator_byte: row.prefix_separator_byte,
|
|
prefix_separator_byte_hex: format!("0x{:02x}", row.prefix_separator_byte),
|
|
primary_name: row.primary_name.clone(),
|
|
secondary_name: row.secondary_name.clone(),
|
|
tertiary_name: row.tertiary_name.clone(),
|
|
},
|
|
)
|
|
.collect::<Vec<_>>();
|
|
let mut compact_prefix_pattern_map =
|
|
BTreeMap::<(u32, u16, u8), PrefixPatternAccumulator>::new();
|
|
let mut name_pair_map = BTreeMap::<(String, String), NamePairAccumulator>::new();
|
|
for row in &embedded_name_rows {
|
|
let entry = compact_prefix_pattern_map
|
|
.entry((
|
|
row.prefix_leading_dword,
|
|
row.prefix_trailing_word,
|
|
row.prefix_separator_byte,
|
|
))
|
|
.or_insert_with(|| PrefixPatternAccumulator {
|
|
first_name_tag_relative_offset: row.name_tag_relative_offset,
|
|
first_primary_name: row.primary_name.clone(),
|
|
first_secondary_name: row.secondary_name.clone(),
|
|
..Default::default()
|
|
});
|
|
entry.count += 1;
|
|
match row.primary_name.as_deref() {
|
|
Some(name) if name.ends_with("_Section.3dp") => {
|
|
entry.section_like_primary_name_count += 1;
|
|
}
|
|
Some(name) if name.ends_with("_Cap.3dp") => {
|
|
entry.cap_like_primary_name_count += 1;
|
|
}
|
|
_ => {
|
|
entry.other_primary_name_count += 1;
|
|
}
|
|
}
|
|
if let (Some(primary_name), Some(secondary_name)) =
|
|
(row.primary_name.as_ref(), row.secondary_name.as_ref())
|
|
{
|
|
let entry = name_pair_map
|
|
.entry((primary_name.clone(), secondary_name.clone()))
|
|
.or_insert_with(|| NamePairAccumulator {
|
|
first_name_tag_relative_offset: row.name_tag_relative_offset,
|
|
..Default::default()
|
|
});
|
|
entry.count += 1;
|
|
*entry
|
|
.prefix_counts
|
|
.entry((
|
|
row.prefix_leading_dword,
|
|
row.prefix_trailing_word,
|
|
row.prefix_separator_byte,
|
|
))
|
|
.or_default() += 1;
|
|
}
|
|
}
|
|
let prefix_leading_dword_matching_embedded_profile_tag_count = embedded_name_rows
|
|
.iter()
|
|
.filter(|row| row.prefix_leading_dword == u32::from(SAVE_REGION_RECORD_PROFILE_TAG))
|
|
.count();
|
|
let mut compact_prefix_pattern_summaries = compact_prefix_pattern_map
|
|
.into_iter()
|
|
.map(
|
|
|(
|
|
(prefix_leading_dword, prefix_trailing_word, prefix_separator_byte),
|
|
accumulator,
|
|
)| {
|
|
SmpSavePlacedStructureDynamicSideBufferPrefixPatternSummary {
|
|
prefix_leading_dword,
|
|
prefix_leading_dword_hex: format!("0x{prefix_leading_dword:08x}"),
|
|
prefix_trailing_word,
|
|
prefix_trailing_word_hex: format!("0x{prefix_trailing_word:04x}"),
|
|
prefix_separator_byte,
|
|
prefix_separator_byte_hex: format!("0x{prefix_separator_byte:02x}"),
|
|
count: accumulator.count,
|
|
first_name_tag_relative_offset: accumulator.first_name_tag_relative_offset,
|
|
prefix_leading_dword_matches_embedded_profile_tag: prefix_leading_dword
|
|
== u32::from(SAVE_REGION_RECORD_PROFILE_TAG),
|
|
section_like_primary_name_count: accumulator
|
|
.section_like_primary_name_count,
|
|
cap_like_primary_name_count: accumulator.cap_like_primary_name_count,
|
|
other_primary_name_count: accumulator.other_primary_name_count,
|
|
first_primary_name: accumulator.first_primary_name,
|
|
first_secondary_name: accumulator.first_secondary_name,
|
|
}
|
|
},
|
|
)
|
|
.collect::<Vec<_>>();
|
|
compact_prefix_pattern_summaries.sort_by(|left, right| {
|
|
right
|
|
.count
|
|
.cmp(&left.count)
|
|
.then_with(|| {
|
|
left.first_name_tag_relative_offset
|
|
.cmp(&right.first_name_tag_relative_offset)
|
|
})
|
|
.then_with(|| left.prefix_leading_dword.cmp(&right.prefix_leading_dword))
|
|
.then_with(|| left.prefix_trailing_word.cmp(&right.prefix_trailing_word))
|
|
.then_with(|| left.prefix_separator_byte.cmp(&right.prefix_separator_byte))
|
|
});
|
|
let mut name_pair_summaries = name_pair_map
|
|
.into_iter()
|
|
.filter_map(|((primary_name, secondary_name), accumulator)| {
|
|
let dominant_prefix = accumulator.prefix_counts.iter().max_by(
|
|
|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
},
|
|
)?;
|
|
let (
|
|
dominant_prefix_leading_dword,
|
|
dominant_prefix_trailing_word,
|
|
dominant_prefix_separator_byte,
|
|
) = *dominant_prefix.0;
|
|
let dominant_prefix_count = *dominant_prefix.1;
|
|
Some(SmpSavePlacedStructureDynamicSideBufferNamePairSummary {
|
|
primary_name,
|
|
secondary_name,
|
|
count: accumulator.count,
|
|
first_name_tag_relative_offset: accumulator.first_name_tag_relative_offset,
|
|
unique_compact_prefix_pattern_count: accumulator.prefix_counts.len(),
|
|
dominant_prefix_leading_dword,
|
|
dominant_prefix_leading_dword_hex: format!(
|
|
"0x{dominant_prefix_leading_dword:08x}"
|
|
),
|
|
dominant_prefix_trailing_word,
|
|
dominant_prefix_trailing_word_hex: format!(
|
|
"0x{dominant_prefix_trailing_word:04x}"
|
|
),
|
|
dominant_prefix_separator_byte,
|
|
dominant_prefix_separator_byte_hex: format!(
|
|
"0x{dominant_prefix_separator_byte:02x}"
|
|
),
|
|
dominant_prefix_count,
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
name_pair_summaries.sort_by(|left, right| {
|
|
right
|
|
.count
|
|
.cmp(&left.count)
|
|
.then_with(|| {
|
|
left.first_name_tag_relative_offset
|
|
.cmp(&right.first_name_tag_relative_offset)
|
|
})
|
|
.then_with(|| left.primary_name.cmp(&right.primary_name))
|
|
.then_with(|| left.secondary_name.cmp(&right.secondary_name))
|
|
});
|
|
let row_count_with_policy_tag_before_next_name = payload_envelope_rows
|
|
.iter()
|
|
.filter(|row| row.policy_tag_relative_offset.is_some())
|
|
.count();
|
|
let row_count_with_complete_0x55f1_0x55f2_0x55f3_envelope = payload_envelope_rows
|
|
.iter()
|
|
.filter(|row| {
|
|
row.policy_tag_relative_offset.is_some()
|
|
&& row.profile_tag_relative_offset.is_some()
|
|
})
|
|
.count();
|
|
let row_count_missing_policy_tag_before_next_name = payload_envelope_rows
|
|
.iter()
|
|
.filter(|row| row.policy_tag_relative_offset.is_none())
|
|
.count();
|
|
let row_count_missing_profile_tag_after_policy = payload_envelope_rows
|
|
.iter()
|
|
.filter(|row| {
|
|
row.policy_tag_relative_offset.is_some()
|
|
&& row.profile_tag_relative_offset.is_none()
|
|
})
|
|
.count();
|
|
let mut policy_chunk_len_counts = BTreeMap::<usize, usize>::new();
|
|
let mut profile_chunk_len_counts = BTreeMap::<usize, usize>::new();
|
|
for row in &payload_envelope_rows {
|
|
if let Some(policy_chunk_len) = row.policy_chunk_len {
|
|
*policy_chunk_len_counts.entry(policy_chunk_len).or_default() += 1;
|
|
}
|
|
if let Some(profile_chunk_len_to_next_name_or_end) =
|
|
row.profile_chunk_len_to_next_name_or_end
|
|
{
|
|
*profile_chunk_len_counts
|
|
.entry(profile_chunk_len_to_next_name_or_end)
|
|
.or_default() += 1;
|
|
}
|
|
}
|
|
let unique_policy_chunk_lens = policy_chunk_len_counts.keys().copied().collect::<Vec<_>>();
|
|
let unique_profile_chunk_lens =
|
|
profile_chunk_len_counts.keys().copied().collect::<Vec<_>>();
|
|
let dominant_policy_chunk_len = policy_chunk_len_counts
|
|
.iter()
|
|
.max_by(|(left_len, left_count), (right_len, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_len.cmp(left_len))
|
|
})
|
|
.map(|(len, count)| (*len, *count));
|
|
let dominant_profile_chunk_len = profile_chunk_len_counts
|
|
.iter()
|
|
.max_by(|(left_len, left_count), (right_len, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_len.cmp(left_len))
|
|
})
|
|
.map(|(len, count)| (*len, *count));
|
|
let short_profile_flag_rows = payload_envelope_rows
|
|
.iter()
|
|
.filter(|row| row.profile_chunk_len_to_next_name_or_end == Some(6))
|
|
.filter_map(|row| {
|
|
Some((
|
|
row.name_tag_relative_offset,
|
|
row.primary_name.clone(),
|
|
row.secondary_name.clone(),
|
|
row.short_profile_first_flag_byte?,
|
|
row.short_profile_second_flag_byte?,
|
|
))
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let mut short_profile_flag_pair_counts = BTreeMap::<(u8, u8), usize>::new();
|
|
let mut short_profile_first_flag_counts = BTreeMap::<u8, usize>::new();
|
|
let mut short_profile_second_flag_counts = BTreeMap::<u8, usize>::new();
|
|
for (_, _, _, first_flag_byte, second_flag_byte) in &short_profile_flag_rows {
|
|
*short_profile_flag_pair_counts
|
|
.entry((*first_flag_byte, *second_flag_byte))
|
|
.or_default() += 1;
|
|
*short_profile_first_flag_counts
|
|
.entry(*first_flag_byte)
|
|
.or_default() += 1;
|
|
*short_profile_second_flag_counts
|
|
.entry(*second_flag_byte)
|
|
.or_default() += 1;
|
|
}
|
|
let dominant_short_profile_flag_pair = short_profile_flag_pair_counts
|
|
.iter()
|
|
.max_by(|(left_pair, left_count), (right_pair, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_pair.cmp(left_pair))
|
|
})
|
|
.map(|((first_flag_byte, second_flag_byte), count)| {
|
|
SmpSavePlacedStructureDynamicSideBufferShortProfileFlagPair {
|
|
first_flag_byte: *first_flag_byte,
|
|
first_flag_byte_hex: format!("0x{first_flag_byte:02x}"),
|
|
second_flag_byte: *second_flag_byte,
|
|
second_flag_byte_hex: format!("0x{second_flag_byte:02x}"),
|
|
count: *count,
|
|
}
|
|
});
|
|
let dominant_short_profile_first_flag = short_profile_first_flag_counts
|
|
.iter()
|
|
.max_by(|(left_byte, left_count), (right_byte, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_byte.cmp(left_byte))
|
|
})
|
|
.map(|(byte, count)| (*byte, *count));
|
|
let dominant_short_profile_second_flag = short_profile_second_flag_counts
|
|
.iter()
|
|
.max_by(|(left_byte, left_count), (right_byte, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_byte.cmp(left_byte))
|
|
})
|
|
.map(|(byte, count)| (*byte, *count));
|
|
let short_profile_flag_pair_summary = Some(
|
|
SmpSavePlacedStructureDynamicSideBufferShortProfileFlagPairSummary {
|
|
row_count_with_0x06_profile_span: short_profile_flag_rows.len(),
|
|
unique_flag_pair_count: short_profile_flag_pair_counts.len(),
|
|
dominant_first_flag_byte: dominant_short_profile_first_flag.map(|(byte, _)| byte),
|
|
dominant_first_flag_byte_hex: dominant_short_profile_first_flag
|
|
.map(|(byte, _)| format!("0x{byte:02x}")),
|
|
dominant_first_flag_byte_count: dominant_short_profile_first_flag
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
dominant_second_flag_byte: dominant_short_profile_second_flag.map(|(byte, _)| byte),
|
|
dominant_second_flag_byte_hex: dominant_short_profile_second_flag
|
|
.map(|(byte, _)| format!("0x{byte:02x}")),
|
|
dominant_second_flag_byte_count: dominant_short_profile_second_flag
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
dominant_flag_pair: dominant_short_profile_flag_pair,
|
|
sample_rows: short_profile_flag_rows
|
|
.iter()
|
|
.take(8)
|
|
.enumerate()
|
|
.map(
|
|
|(
|
|
sample_index,
|
|
(
|
|
name_tag_relative_offset,
|
|
primary_name,
|
|
secondary_name,
|
|
first_flag_byte,
|
|
second_flag_byte,
|
|
),
|
|
)| {
|
|
SmpSavePlacedStructureDynamicSideBufferShortProfileFlagPairSample {
|
|
sample_index,
|
|
name_tag_relative_offset: *name_tag_relative_offset,
|
|
primary_name: primary_name.clone(),
|
|
secondary_name: secondary_name.clone(),
|
|
first_flag_byte: *first_flag_byte,
|
|
first_flag_byte_hex: format!("0x{first_flag_byte:02x}"),
|
|
second_flag_byte: *second_flag_byte,
|
|
second_flag_byte_hex: format!("0x{second_flag_byte:02x}"),
|
|
}
|
|
},
|
|
)
|
|
.collect(),
|
|
},
|
|
);
|
|
let classify_side_buffer_mode_family =
|
|
|primary_name: Option<&str>, secondary_name: Option<&str>| -> &'static str {
|
|
let name = primary_name.or(secondary_name).unwrap_or_default();
|
|
if name.starts_with("Bridge") {
|
|
"bridge"
|
|
} else if name.starts_with("Tunnel") {
|
|
"tunnel"
|
|
} else if name.starts_with("BallastCap") {
|
|
"ballast_cap"
|
|
} else if name.starts_with("TrackCap") {
|
|
"track_cap"
|
|
} else if name.starts_with("Overpass") {
|
|
"overpass"
|
|
} else if name.is_empty() {
|
|
"unknown"
|
|
} else {
|
|
"other"
|
|
}
|
|
};
|
|
let fixed_policy_rows = payload_envelope_rows
|
|
.iter()
|
|
.filter(|row| row.policy_chunk_len == Some(0x1a))
|
|
.filter_map(|row| {
|
|
let embedded_name_row = embedded_name_rows
|
|
.iter()
|
|
.find(|entry| entry.name_tag_relative_offset == row.name_tag_relative_offset)?;
|
|
let policy_tag_relative_offset = row.policy_tag_relative_offset?;
|
|
let policy_payload = records_payload
|
|
.get(policy_tag_relative_offset + 4..policy_tag_relative_offset + 4 + 0x1a)?;
|
|
let first_triplet_dwords = [
|
|
read_u32_at(policy_payload, 0)?,
|
|
read_u32_at(policy_payload, 4)?,
|
|
read_u32_at(policy_payload, 8)?,
|
|
];
|
|
let second_triplet_dwords = [
|
|
read_u32_at(policy_payload, 12)?,
|
|
read_u32_at(policy_payload, 16)?,
|
|
read_u32_at(policy_payload, 20)?,
|
|
];
|
|
let trailing_word = read_u16_at(policy_payload, 24)?;
|
|
Some((
|
|
row.name_tag_relative_offset,
|
|
row.primary_name.clone(),
|
|
row.secondary_name.clone(),
|
|
embedded_name_row.prefix_leading_dword,
|
|
embedded_name_row.prefix_trailing_word,
|
|
embedded_name_row.prefix_separator_byte,
|
|
first_triplet_dwords,
|
|
second_triplet_dwords,
|
|
trailing_word,
|
|
))
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let mut fixed_policy_trailing_word_counts = BTreeMap::<u16, usize>::new();
|
|
for (_, _, _, _, _, _, _, _, trailing_word) in &fixed_policy_rows {
|
|
*fixed_policy_trailing_word_counts
|
|
.entry(*trailing_word)
|
|
.or_default() += 1;
|
|
}
|
|
let dominant_fixed_policy_trailing_word = fixed_policy_trailing_word_counts
|
|
.iter()
|
|
.max_by(|(left_word, left_count), (right_word, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_word.cmp(left_word))
|
|
})
|
|
.map(|(word, count)| (*word, *count));
|
|
let mut fixed_policy_compact_prefix_groups = BTreeMap::<
|
|
(u32, u16, u8),
|
|
Vec<(
|
|
usize,
|
|
Option<String>,
|
|
Option<String>,
|
|
[u32; 3],
|
|
[u32; 3],
|
|
u16,
|
|
)>,
|
|
>::new();
|
|
for (
|
|
name_tag_relative_offset,
|
|
primary_name,
|
|
secondary_name,
|
|
prefix_leading_dword,
|
|
prefix_trailing_word,
|
|
prefix_separator_byte,
|
|
first_triplet_dwords,
|
|
second_triplet_dwords,
|
|
trailing_word,
|
|
) in &fixed_policy_rows
|
|
{
|
|
fixed_policy_compact_prefix_groups
|
|
.entry((
|
|
*prefix_leading_dword,
|
|
*prefix_trailing_word,
|
|
*prefix_separator_byte,
|
|
))
|
|
.or_default()
|
|
.push((
|
|
*name_tag_relative_offset,
|
|
primary_name.clone(),
|
|
secondary_name.clone(),
|
|
*first_triplet_dwords,
|
|
*second_triplet_dwords,
|
|
*trailing_word,
|
|
));
|
|
}
|
|
let fixed_policy_compact_prefix_correlations = fixed_policy_compact_prefix_groups
|
|
.into_iter()
|
|
.map(
|
|
|((prefix_leading_dword, prefix_trailing_word, prefix_separator_byte), rows)| {
|
|
let mut name_pair_counts =
|
|
BTreeMap::<(Option<String>, Option<String>), usize>::new();
|
|
let mut mode_family_counts = BTreeMap::<String, usize>::new();
|
|
let mut policy_tuple_counts =
|
|
BTreeMap::<([u32; 3], [u32; 3], u16), usize>::new();
|
|
for (
|
|
_name_tag_relative_offset,
|
|
primary_name,
|
|
secondary_name,
|
|
first_triplet_dwords,
|
|
second_triplet_dwords,
|
|
trailing_word,
|
|
) in &rows
|
|
{
|
|
*name_pair_counts
|
|
.entry((primary_name.clone(), secondary_name.clone()))
|
|
.or_default() += 1;
|
|
*mode_family_counts
|
|
.entry(
|
|
classify_side_buffer_mode_family(
|
|
primary_name.as_deref(),
|
|
secondary_name.as_deref(),
|
|
)
|
|
.to_string(),
|
|
)
|
|
.or_default() += 1;
|
|
*policy_tuple_counts
|
|
.entry((
|
|
*first_triplet_dwords,
|
|
*second_triplet_dwords,
|
|
*trailing_word,
|
|
))
|
|
.or_default() += 1;
|
|
}
|
|
let dominant_name_pair = name_pair_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|((primary_name, secondary_name), count)| {
|
|
(primary_name.clone(), secondary_name.clone(), *count)
|
|
});
|
|
let dominant_mode_family = mode_family_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|(mode_family, count)| (mode_family.clone(), *count));
|
|
let dominant_policy_tuple = policy_tuple_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(
|
|
|(
|
|
(first_triplet_dwords, second_triplet_dwords, trailing_word),
|
|
count,
|
|
)| {
|
|
(
|
|
*first_triplet_dwords,
|
|
*second_triplet_dwords,
|
|
*trailing_word,
|
|
*count,
|
|
)
|
|
},
|
|
);
|
|
SmpSavePlacedStructureDynamicSideBufferFixedPolicyCompactPrefixCorrelation {
|
|
prefix_leading_dword,
|
|
prefix_leading_dword_hex: format!("0x{prefix_leading_dword:08x}"),
|
|
prefix_trailing_word,
|
|
prefix_trailing_word_hex: format!("0x{prefix_trailing_word:04x}"),
|
|
prefix_separator_byte,
|
|
prefix_separator_byte_hex: format!("0x{prefix_separator_byte:02x}"),
|
|
row_count: rows.len(),
|
|
unique_policy_tuple_count: policy_tuple_counts.len(),
|
|
dominant_primary_name: dominant_name_pair
|
|
.as_ref()
|
|
.and_then(|(primary_name, _, _)| primary_name.clone()),
|
|
dominant_secondary_name: dominant_name_pair
|
|
.as_ref()
|
|
.and_then(|(_, secondary_name, _)| secondary_name.clone()),
|
|
dominant_name_pair_count: dominant_name_pair
|
|
.map(|(_, _, count)| count)
|
|
.unwrap_or_default(),
|
|
dominant_mode_family: dominant_mode_family
|
|
.as_ref()
|
|
.map(|(mode_family, _)| mode_family.clone()),
|
|
dominant_mode_family_count: dominant_mode_family
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
dominant_first_triplet_dwords_hex: dominant_policy_tuple
|
|
.as_ref()
|
|
.map(|(first_triplet_dwords, _, _, _)| {
|
|
first_triplet_dwords
|
|
.iter()
|
|
.map(|value| format!("0x{value:08x}"))
|
|
.collect()
|
|
})
|
|
.unwrap_or_default(),
|
|
dominant_second_triplet_dwords_hex: dominant_policy_tuple
|
|
.as_ref()
|
|
.map(|(_, second_triplet_dwords, _, _)| {
|
|
second_triplet_dwords
|
|
.iter()
|
|
.map(|value| format!("0x{value:08x}"))
|
|
.collect()
|
|
})
|
|
.unwrap_or_default(),
|
|
dominant_trailing_word: dominant_policy_tuple
|
|
.map(|(_, _, trailing_word, _)| trailing_word),
|
|
dominant_trailing_word_hex: dominant_policy_tuple
|
|
.map(|(_, _, trailing_word, _)| format!("0x{trailing_word:04x}")),
|
|
dominant_policy_tuple_count: dominant_policy_tuple
|
|
.map(|(_, _, _, count)| count)
|
|
.unwrap_or_default(),
|
|
mode_family_counts: mode_family_counts
|
|
.into_iter()
|
|
.map(|(mode_family, count)| {
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount {
|
|
mode_family,
|
|
count,
|
|
}
|
|
})
|
|
.collect(),
|
|
sample_rows: rows
|
|
.iter()
|
|
.take(8)
|
|
.enumerate()
|
|
.map(
|
|
|(
|
|
sample_index,
|
|
(
|
|
name_tag_relative_offset,
|
|
primary_name,
|
|
secondary_name,
|
|
first_triplet_dwords,
|
|
second_triplet_dwords,
|
|
trailing_word,
|
|
),
|
|
)| {
|
|
SmpSavePlacedStructureDynamicSideBufferFixedPolicySample {
|
|
sample_index,
|
|
name_tag_relative_offset: *name_tag_relative_offset,
|
|
primary_name: primary_name.clone(),
|
|
secondary_name: secondary_name.clone(),
|
|
first_triplet_dwords_hex: first_triplet_dwords
|
|
.iter()
|
|
.map(|value| format!("0x{value:08x}"))
|
|
.collect(),
|
|
second_triplet_dwords_hex: second_triplet_dwords
|
|
.iter()
|
|
.map(|value| format!("0x{value:08x}"))
|
|
.collect(),
|
|
trailing_word: *trailing_word,
|
|
trailing_word_hex: format!("0x{trailing_word:04x}"),
|
|
}
|
|
},
|
|
)
|
|
.collect(),
|
|
}
|
|
},
|
|
)
|
|
.take(8)
|
|
.collect::<Vec<_>>();
|
|
let fixed_policy_summary =
|
|
Some(SmpSavePlacedStructureDynamicSideBufferFixedPolicySummary {
|
|
row_count_with_0x1a_policy_chunk: fixed_policy_rows.len(),
|
|
unique_trailing_word_count: fixed_policy_trailing_word_counts.len(),
|
|
dominant_trailing_word: dominant_fixed_policy_trailing_word.map(|(word, _)| word),
|
|
dominant_trailing_word_hex: dominant_fixed_policy_trailing_word
|
|
.map(|(word, _)| format!("0x{word:04x}")),
|
|
dominant_trailing_word_count: dominant_fixed_policy_trailing_word
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
compact_prefix_correlations: fixed_policy_compact_prefix_correlations,
|
|
sample_rows: fixed_policy_rows
|
|
.iter()
|
|
.take(8)
|
|
.enumerate()
|
|
.map(
|
|
|(
|
|
sample_index,
|
|
(
|
|
name_tag_relative_offset,
|
|
primary_name,
|
|
secondary_name,
|
|
_prefix_leading_dword,
|
|
_prefix_trailing_word,
|
|
_prefix_separator_byte,
|
|
first_triplet_dwords,
|
|
second_triplet_dwords,
|
|
trailing_word,
|
|
),
|
|
)| {
|
|
SmpSavePlacedStructureDynamicSideBufferFixedPolicySample {
|
|
sample_index,
|
|
name_tag_relative_offset: *name_tag_relative_offset,
|
|
primary_name: primary_name.clone(),
|
|
secondary_name: secondary_name.clone(),
|
|
first_triplet_dwords_hex: first_triplet_dwords
|
|
.iter()
|
|
.map(|value| format!("0x{value:08x}"))
|
|
.collect(),
|
|
second_triplet_dwords_hex: second_triplet_dwords
|
|
.iter()
|
|
.map(|value| format!("0x{value:08x}"))
|
|
.collect(),
|
|
trailing_word: *trailing_word,
|
|
trailing_word_hex: format!("0x{trailing_word:04x}"),
|
|
}
|
|
},
|
|
)
|
|
.collect(),
|
|
});
|
|
let name_prelude_candidate_rows = embedded_name_rows
|
|
.iter()
|
|
.enumerate()
|
|
.filter_map(|(row_index, row)| {
|
|
let candidate_offset = row.name_tag_relative_offset.checked_sub(3)?;
|
|
let child_count_candidate = read_u16_at(records_payload, candidate_offset)?;
|
|
let saved_primary_child_byte_candidate =
|
|
read_u8_at(records_payload, candidate_offset + 2)?;
|
|
Some((
|
|
row.name_tag_relative_offset,
|
|
row.primary_name.clone(),
|
|
row.secondary_name.clone(),
|
|
row.prefix_leading_dword,
|
|
row.prefix_trailing_word,
|
|
row.prefix_separator_byte,
|
|
child_count_candidate,
|
|
saved_primary_child_byte_candidate,
|
|
row_index.checked_sub(1).and_then(|previous_index| {
|
|
payload_envelope_rows
|
|
.get(previous_index)
|
|
.and_then(|row| row.profile_chunk_len_to_next_name_or_end)
|
|
}),
|
|
row_index.checked_sub(1).and_then(|previous_index| {
|
|
payload_envelope_rows.get(previous_index).and_then(|row| {
|
|
row.short_profile_first_flag_byte
|
|
.zip(row.short_profile_second_flag_byte)
|
|
})
|
|
}),
|
|
))
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let mut name_prelude_candidate_pattern_counts = BTreeMap::<(u16, u8), usize>::new();
|
|
let mut name_prelude_child_count_counts = BTreeMap::<u16, usize>::new();
|
|
let mut name_prelude_saved_primary_counts = BTreeMap::<u8, usize>::new();
|
|
for (_, _, _, _, _, _, child_count_candidate, saved_primary_child_byte_candidate, _, _) in
|
|
&name_prelude_candidate_rows
|
|
{
|
|
*name_prelude_candidate_pattern_counts
|
|
.entry((*child_count_candidate, *saved_primary_child_byte_candidate))
|
|
.or_default() += 1;
|
|
*name_prelude_child_count_counts
|
|
.entry(*child_count_candidate)
|
|
.or_default() += 1;
|
|
*name_prelude_saved_primary_counts
|
|
.entry(*saved_primary_child_byte_candidate)
|
|
.or_default() += 1;
|
|
}
|
|
let dominant_name_prelude_candidate_pattern = name_prelude_candidate_pattern_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(
|
|
|((child_count_candidate, saved_primary_child_byte_candidate), count)| {
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern {
|
|
child_count_candidate: *child_count_candidate,
|
|
child_count_candidate_hex: format!("0x{child_count_candidate:04x}"),
|
|
saved_primary_child_byte_candidate: *saved_primary_child_byte_candidate,
|
|
saved_primary_child_byte_candidate_hex: format!(
|
|
"0x{saved_primary_child_byte_candidate:02x}"
|
|
),
|
|
count: *count,
|
|
}
|
|
},
|
|
);
|
|
let dominant_name_prelude_child_count = name_prelude_child_count_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|(value, count)| (*value, *count));
|
|
let dominant_name_prelude_saved_primary = name_prelude_saved_primary_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|(value, count)| (*value, *count));
|
|
let mut name_prelude_pattern_groups = BTreeMap::<
|
|
(u16, u8),
|
|
Vec<(usize, Option<String>, Option<String>, Option<usize>)>,
|
|
>::new();
|
|
for (
|
|
name_tag_relative_offset,
|
|
primary_name,
|
|
secondary_name,
|
|
_prefix_leading_dword,
|
|
_prefix_trailing_word,
|
|
_prefix_separator_byte,
|
|
child_count_candidate,
|
|
saved_primary_child_byte_candidate,
|
|
previous_span,
|
|
_previous_short_profile_flag_pair,
|
|
) in &name_prelude_candidate_rows
|
|
{
|
|
name_prelude_pattern_groups
|
|
.entry((*child_count_candidate, *saved_primary_child_byte_candidate))
|
|
.or_default()
|
|
.push((
|
|
*name_tag_relative_offset,
|
|
primary_name.clone(),
|
|
secondary_name.clone(),
|
|
*previous_span,
|
|
));
|
|
}
|
|
let candidate_pattern_correlations = name_prelude_pattern_groups
|
|
.into_iter()
|
|
.map(
|
|
|(
|
|
(child_count_candidate, saved_primary_child_byte_candidate),
|
|
rows,
|
|
)| {
|
|
let mut name_pair_counts =
|
|
BTreeMap::<(Option<String>, Option<String>), usize>::new();
|
|
let mut profile_span_counts = BTreeMap::<usize, usize>::new();
|
|
let mut mode_family_counts = BTreeMap::<String, usize>::new();
|
|
for (_, primary_name, secondary_name, previous_span) in &rows {
|
|
*name_pair_counts
|
|
.entry((primary_name.clone(), secondary_name.clone()))
|
|
.or_default() += 1;
|
|
*mode_family_counts
|
|
.entry(
|
|
classify_side_buffer_mode_family(
|
|
primary_name.as_deref(),
|
|
secondary_name.as_deref(),
|
|
)
|
|
.to_string(),
|
|
)
|
|
.or_default() += 1;
|
|
if let Some(previous_span) = previous_span {
|
|
*profile_span_counts.entry(*previous_span).or_default() += 1;
|
|
}
|
|
}
|
|
let dominant_name_pair = name_pair_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|((primary_name, secondary_name), count)| {
|
|
(primary_name.clone(), secondary_name.clone(), *count)
|
|
});
|
|
let dominant_profile_span = profile_span_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|(span, count)| (*span, *count));
|
|
let dominant_mode_family = mode_family_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|(mode_family, count)| (mode_family.clone(), *count));
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePatternCorrelation {
|
|
child_count_candidate,
|
|
child_count_candidate_hex: format!("0x{child_count_candidate:04x}"),
|
|
saved_primary_child_byte_candidate,
|
|
saved_primary_child_byte_candidate_hex: format!(
|
|
"0x{saved_primary_child_byte_candidate:02x}"
|
|
),
|
|
row_count: rows.len(),
|
|
unique_name_pair_count: name_pair_counts.len(),
|
|
unique_profile_span_count: profile_span_counts.len(),
|
|
dominant_primary_name: dominant_name_pair
|
|
.as_ref()
|
|
.and_then(|(primary_name, _, _)| primary_name.clone()),
|
|
dominant_secondary_name: dominant_name_pair
|
|
.as_ref()
|
|
.and_then(|(_, secondary_name, _)| secondary_name.clone()),
|
|
dominant_name_pair_count: dominant_name_pair
|
|
.map(|(_, _, count)| count)
|
|
.unwrap_or_default(),
|
|
dominant_profile_span: dominant_profile_span
|
|
.map(|(profile_span, _)| profile_span),
|
|
dominant_profile_span_count: dominant_profile_span
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
dominant_mode_family: dominant_mode_family
|
|
.as_ref()
|
|
.map(|(mode_family, _)| mode_family.clone()),
|
|
dominant_mode_family_count: dominant_mode_family
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
mode_family_counts: mode_family_counts
|
|
.into_iter()
|
|
.map(|(mode_family, count)| {
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount {
|
|
mode_family,
|
|
count,
|
|
}
|
|
})
|
|
.collect(),
|
|
sample_rows: rows
|
|
.iter()
|
|
.take(8)
|
|
.enumerate()
|
|
.map(
|
|
|(
|
|
sample_index,
|
|
(
|
|
name_tag_relative_offset,
|
|
primary_name,
|
|
secondary_name,
|
|
previous_profile_chunk_len_to_next_name_or_end,
|
|
),
|
|
)| {
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidateSample {
|
|
sample_index,
|
|
name_tag_relative_offset: *name_tag_relative_offset,
|
|
primary_name: primary_name.clone(),
|
|
secondary_name: secondary_name.clone(),
|
|
child_count_candidate,
|
|
child_count_candidate_hex: format!(
|
|
"0x{child_count_candidate:04x}"
|
|
),
|
|
saved_primary_child_byte_candidate,
|
|
saved_primary_child_byte_candidate_hex: format!(
|
|
"0x{saved_primary_child_byte_candidate:02x}"
|
|
),
|
|
previous_profile_chunk_len_to_next_name_or_end:
|
|
*previous_profile_chunk_len_to_next_name_or_end,
|
|
}
|
|
},
|
|
)
|
|
.collect(),
|
|
}
|
|
},
|
|
)
|
|
.take(8)
|
|
.collect::<Vec<_>>();
|
|
let mut name_prelude_profile_span_groups = BTreeMap::<
|
|
usize,
|
|
Vec<(usize, Option<String>, Option<String>, u32, u16, u8, u16, u8)>,
|
|
>::new();
|
|
for (
|
|
name_tag_relative_offset,
|
|
primary_name,
|
|
secondary_name,
|
|
prefix_leading_dword,
|
|
prefix_trailing_word,
|
|
prefix_separator_byte,
|
|
child_count_candidate,
|
|
saved_primary_child_byte_candidate,
|
|
previous_span,
|
|
_previous_short_profile_flag_pair,
|
|
) in &name_prelude_candidate_rows
|
|
{
|
|
if let Some(previous_span) = previous_span {
|
|
name_prelude_profile_span_groups
|
|
.entry(*previous_span)
|
|
.or_default()
|
|
.push((
|
|
*name_tag_relative_offset,
|
|
primary_name.clone(),
|
|
secondary_name.clone(),
|
|
*prefix_leading_dword,
|
|
*prefix_trailing_word,
|
|
*prefix_separator_byte,
|
|
*child_count_candidate,
|
|
*saved_primary_child_byte_candidate,
|
|
));
|
|
}
|
|
}
|
|
let profile_span_correlations = name_prelude_profile_span_groups
|
|
.into_iter()
|
|
.map(|(previous_profile_chunk_len_to_next_name_or_end, rows)| {
|
|
let mut pattern_counts = BTreeMap::<(u16, u8), usize>::new();
|
|
let mut child_count_counts = BTreeMap::<u16, usize>::new();
|
|
let mut saved_primary_counts = BTreeMap::<u8, usize>::new();
|
|
let mut mode_family_counts = BTreeMap::<String, usize>::new();
|
|
let mut prefix_counts = BTreeMap::<(u32, u16, u8), usize>::new();
|
|
for (
|
|
_name_tag_relative_offset,
|
|
primary_name,
|
|
secondary_name,
|
|
prefix_leading_dword,
|
|
prefix_trailing_word,
|
|
prefix_separator_byte,
|
|
child_count_candidate,
|
|
saved_primary_child_byte_candidate,
|
|
) in &rows
|
|
{
|
|
*pattern_counts
|
|
.entry((*child_count_candidate, *saved_primary_child_byte_candidate))
|
|
.or_default() += 1;
|
|
*child_count_counts
|
|
.entry(*child_count_candidate)
|
|
.or_default() += 1;
|
|
*saved_primary_counts
|
|
.entry(*saved_primary_child_byte_candidate)
|
|
.or_default() += 1;
|
|
*prefix_counts
|
|
.entry((
|
|
*prefix_leading_dword,
|
|
*prefix_trailing_word,
|
|
*prefix_separator_byte,
|
|
))
|
|
.or_default() += 1;
|
|
*mode_family_counts
|
|
.entry(
|
|
classify_side_buffer_mode_family(
|
|
primary_name.as_deref(),
|
|
secondary_name.as_deref(),
|
|
)
|
|
.to_string(),
|
|
)
|
|
.or_default() += 1;
|
|
}
|
|
let dominant_pattern = pattern_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(
|
|
|((child_count_candidate, saved_primary_child_byte_candidate), count)| {
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern {
|
|
child_count_candidate: *child_count_candidate,
|
|
child_count_candidate_hex: format!("0x{child_count_candidate:04x}"),
|
|
saved_primary_child_byte_candidate:
|
|
*saved_primary_child_byte_candidate,
|
|
saved_primary_child_byte_candidate_hex: format!(
|
|
"0x{saved_primary_child_byte_candidate:02x}"
|
|
),
|
|
count: *count,
|
|
}
|
|
},
|
|
);
|
|
let dominant_child_count = child_count_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|(value, count)| (*value, *count));
|
|
let dominant_saved_primary = saved_primary_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|(value, count)| (*value, *count));
|
|
let dominant_mode_family = mode_family_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|(mode_family, count)| (mode_family.clone(), *count));
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanCorrelation {
|
|
previous_profile_chunk_len_to_next_name_or_end,
|
|
row_count: rows.len(),
|
|
dominant_child_count_candidate: dominant_child_count.map(|(value, _)| value),
|
|
dominant_child_count_candidate_count: dominant_child_count
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
dominant_saved_primary_child_byte_candidate: dominant_saved_primary
|
|
.map(|(value, _)| value),
|
|
dominant_saved_primary_child_byte_candidate_hex: dominant_saved_primary
|
|
.map(|(value, _)| format!("0x{value:02x}")),
|
|
dominant_saved_primary_child_byte_candidate_count: dominant_saved_primary
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
dominant_candidate_pattern: dominant_pattern,
|
|
dominant_mode_family: dominant_mode_family
|
|
.as_ref()
|
|
.map(|(mode_family, _)| mode_family.clone()),
|
|
dominant_mode_family_count: dominant_mode_family
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
mode_family_counts: mode_family_counts
|
|
.into_iter()
|
|
.map(|(mode_family, count)| {
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount {
|
|
mode_family,
|
|
count,
|
|
}
|
|
})
|
|
.collect(),
|
|
compact_prefix_pattern_summaries: prefix_counts
|
|
.into_iter()
|
|
.map(
|
|
|(
|
|
(prefix_leading_dword, prefix_trailing_word, prefix_separator_byte),
|
|
count,
|
|
)| {
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanPrefixSummary {
|
|
prefix_leading_dword,
|
|
prefix_leading_dword_hex: format!(
|
|
"0x{prefix_leading_dword:08x}"
|
|
),
|
|
prefix_trailing_word,
|
|
prefix_trailing_word_hex: format!(
|
|
"0x{prefix_trailing_word:04x}"
|
|
),
|
|
prefix_separator_byte,
|
|
prefix_separator_byte_hex: format!(
|
|
"0x{prefix_separator_byte:02x}"
|
|
),
|
|
count,
|
|
}
|
|
},
|
|
)
|
|
.collect(),
|
|
sample_rows: rows
|
|
.iter()
|
|
.take(8)
|
|
.enumerate()
|
|
.map(
|
|
|(
|
|
sample_index,
|
|
(
|
|
name_tag_relative_offset,
|
|
primary_name,
|
|
secondary_name,
|
|
prefix_leading_dword,
|
|
prefix_trailing_word,
|
|
prefix_separator_byte,
|
|
child_count_candidate,
|
|
saved_primary_child_byte_candidate,
|
|
),
|
|
)| {
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanSample {
|
|
sample_index,
|
|
name_tag_relative_offset: *name_tag_relative_offset,
|
|
primary_name: primary_name.clone(),
|
|
secondary_name: secondary_name.clone(),
|
|
prefix_leading_dword: *prefix_leading_dword,
|
|
prefix_leading_dword_hex: format!(
|
|
"0x{prefix_leading_dword:08x}"
|
|
),
|
|
prefix_trailing_word: *prefix_trailing_word,
|
|
prefix_trailing_word_hex: format!(
|
|
"0x{prefix_trailing_word:04x}"
|
|
),
|
|
prefix_separator_byte: *prefix_separator_byte,
|
|
prefix_separator_byte_hex: format!(
|
|
"0x{prefix_separator_byte:02x}"
|
|
),
|
|
child_count_candidate: *child_count_candidate,
|
|
child_count_candidate_hex: format!(
|
|
"0x{child_count_candidate:04x}"
|
|
),
|
|
saved_primary_child_byte_candidate:
|
|
*saved_primary_child_byte_candidate,
|
|
saved_primary_child_byte_candidate_hex: format!(
|
|
"0x{saved_primary_child_byte_candidate:02x}"
|
|
),
|
|
}
|
|
},
|
|
)
|
|
.collect(),
|
|
}
|
|
})
|
|
.take(8)
|
|
.collect::<Vec<_>>();
|
|
let mut name_prelude_compact_prefix_groups = BTreeMap::<
|
|
(u32, u16, u8),
|
|
Vec<(
|
|
usize,
|
|
Option<String>,
|
|
Option<String>,
|
|
u16,
|
|
u8,
|
|
Option<usize>,
|
|
Option<(u8, u8)>,
|
|
)>,
|
|
>::new();
|
|
for (
|
|
name_tag_relative_offset,
|
|
primary_name,
|
|
secondary_name,
|
|
prefix_leading_dword,
|
|
prefix_trailing_word,
|
|
prefix_separator_byte,
|
|
child_count_candidate,
|
|
saved_primary_child_byte_candidate,
|
|
previous_span,
|
|
previous_short_profile_flag_pair,
|
|
) in &name_prelude_candidate_rows
|
|
{
|
|
name_prelude_compact_prefix_groups
|
|
.entry((
|
|
*prefix_leading_dword,
|
|
*prefix_trailing_word,
|
|
*prefix_separator_byte,
|
|
))
|
|
.or_default()
|
|
.push((
|
|
*name_tag_relative_offset,
|
|
primary_name.clone(),
|
|
secondary_name.clone(),
|
|
*child_count_candidate,
|
|
*saved_primary_child_byte_candidate,
|
|
*previous_span,
|
|
*previous_short_profile_flag_pair,
|
|
));
|
|
}
|
|
let compact_prefix_correlations = name_prelude_compact_prefix_groups
|
|
.into_iter()
|
|
.map(
|
|
|(
|
|
(prefix_leading_dword, prefix_trailing_word, prefix_separator_byte),
|
|
rows,
|
|
)| {
|
|
let mut name_pair_counts =
|
|
BTreeMap::<(Option<String>, Option<String>), usize>::new();
|
|
let mut profile_span_counts = BTreeMap::<usize, usize>::new();
|
|
let mut candidate_pattern_counts = BTreeMap::<(u16, u8), usize>::new();
|
|
let mut mode_family_counts = BTreeMap::<String, usize>::new();
|
|
let mut flag_pair_counts = BTreeMap::<(u8, u8), usize>::new();
|
|
for (
|
|
_name_tag_relative_offset,
|
|
primary_name,
|
|
secondary_name,
|
|
child_count_candidate,
|
|
saved_primary_child_byte_candidate,
|
|
previous_span,
|
|
previous_short_profile_flag_pair,
|
|
) in &rows
|
|
{
|
|
*name_pair_counts
|
|
.entry((primary_name.clone(), secondary_name.clone()))
|
|
.or_default() += 1;
|
|
if let Some(previous_span) = previous_span {
|
|
*profile_span_counts.entry(*previous_span).or_default() += 1;
|
|
}
|
|
*candidate_pattern_counts
|
|
.entry((*child_count_candidate, *saved_primary_child_byte_candidate))
|
|
.or_default() += 1;
|
|
*mode_family_counts
|
|
.entry(
|
|
classify_side_buffer_mode_family(
|
|
primary_name.as_deref(),
|
|
secondary_name.as_deref(),
|
|
)
|
|
.to_string(),
|
|
)
|
|
.or_default() += 1;
|
|
if let Some((first_flag_byte, second_flag_byte)) =
|
|
previous_short_profile_flag_pair
|
|
{
|
|
*flag_pair_counts
|
|
.entry((*first_flag_byte, *second_flag_byte))
|
|
.or_default() += 1;
|
|
}
|
|
}
|
|
let dominant_name_pair = name_pair_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|((primary_name, secondary_name), count)| {
|
|
(primary_name.clone(), secondary_name.clone(), *count)
|
|
});
|
|
let dominant_profile_span = profile_span_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|(span, count)| (*span, *count));
|
|
let dominant_candidate_pattern = candidate_pattern_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(
|
|
|(
|
|
(child_count_candidate, saved_primary_child_byte_candidate),
|
|
count,
|
|
)| {
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern {
|
|
child_count_candidate: *child_count_candidate,
|
|
child_count_candidate_hex: format!(
|
|
"0x{child_count_candidate:04x}"
|
|
),
|
|
saved_primary_child_byte_candidate:
|
|
*saved_primary_child_byte_candidate,
|
|
saved_primary_child_byte_candidate_hex: format!(
|
|
"0x{saved_primary_child_byte_candidate:02x}"
|
|
),
|
|
count: *count,
|
|
}
|
|
},
|
|
);
|
|
let dominant_mode_family = mode_family_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|(mode_family, count)| (mode_family.clone(), *count));
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixCorrelation {
|
|
prefix_leading_dword,
|
|
prefix_leading_dword_hex: format!("0x{prefix_leading_dword:08x}"),
|
|
prefix_trailing_word,
|
|
prefix_trailing_word_hex: format!("0x{prefix_trailing_word:04x}"),
|
|
prefix_separator_byte,
|
|
prefix_separator_byte_hex: format!("0x{prefix_separator_byte:02x}"),
|
|
row_count: rows.len(),
|
|
unique_name_pair_count: name_pair_counts.len(),
|
|
unique_profile_span_count: profile_span_counts.len(),
|
|
dominant_primary_name: dominant_name_pair
|
|
.as_ref()
|
|
.and_then(|(primary_name, _, _)| primary_name.clone()),
|
|
dominant_secondary_name: dominant_name_pair
|
|
.as_ref()
|
|
.and_then(|(_, secondary_name, _)| secondary_name.clone()),
|
|
dominant_name_pair_count: dominant_name_pair
|
|
.map(|(_, _, count)| count)
|
|
.unwrap_or_default(),
|
|
dominant_profile_span: dominant_profile_span
|
|
.map(|(profile_span, _)| profile_span),
|
|
dominant_profile_span_count: dominant_profile_span
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
dominant_candidate_pattern,
|
|
dominant_mode_family: dominant_mode_family
|
|
.as_ref()
|
|
.map(|(mode_family, _)| mode_family.clone()),
|
|
dominant_mode_family_count: dominant_mode_family
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
mode_family_counts: mode_family_counts
|
|
.into_iter()
|
|
.map(|(mode_family, count)| {
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount {
|
|
mode_family,
|
|
count,
|
|
}
|
|
})
|
|
.collect(),
|
|
name_pair_summaries: name_pair_counts
|
|
.into_iter()
|
|
.map(|((primary_name, secondary_name), count)| {
|
|
SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanNamePairSummary {
|
|
primary_name,
|
|
secondary_name,
|
|
count,
|
|
}
|
|
})
|
|
.collect(),
|
|
profile_span_counts: profile_span_counts
|
|
.into_iter()
|
|
.map(
|
|
|(previous_profile_chunk_len_to_next_name_or_end, count)| {
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixProfileSpanCount {
|
|
previous_profile_chunk_len_to_next_name_or_end,
|
|
count,
|
|
}
|
|
},
|
|
)
|
|
.collect(),
|
|
rows_with_previous_short_profile_flag_pair: flag_pair_counts
|
|
.values()
|
|
.copied()
|
|
.sum(),
|
|
previous_short_profile_flag_pair_counts: flag_pair_counts
|
|
.into_iter()
|
|
.map(|((first_flag_byte, second_flag_byte), count)| {
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixFlagPairCount {
|
|
first_flag_byte,
|
|
first_flag_byte_hex: format!("0x{first_flag_byte:02x}"),
|
|
second_flag_byte,
|
|
second_flag_byte_hex: format!("0x{second_flag_byte:02x}"),
|
|
count,
|
|
}
|
|
})
|
|
.collect(),
|
|
sample_rows: rows
|
|
.iter()
|
|
.take(8)
|
|
.enumerate()
|
|
.map(
|
|
|(
|
|
sample_index,
|
|
(
|
|
name_tag_relative_offset,
|
|
primary_name,
|
|
secondary_name,
|
|
child_count_candidate,
|
|
saved_primary_child_byte_candidate,
|
|
previous_profile_chunk_len_to_next_name_or_end,
|
|
_previous_short_profile_flag_pair,
|
|
),
|
|
)| {
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixSample {
|
|
sample_index,
|
|
name_tag_relative_offset: *name_tag_relative_offset,
|
|
primary_name: primary_name.clone(),
|
|
secondary_name: secondary_name.clone(),
|
|
child_count_candidate: *child_count_candidate,
|
|
child_count_candidate_hex: format!(
|
|
"0x{child_count_candidate:04x}"
|
|
),
|
|
saved_primary_child_byte_candidate:
|
|
*saved_primary_child_byte_candidate,
|
|
saved_primary_child_byte_candidate_hex: format!(
|
|
"0x{saved_primary_child_byte_candidate:02x}"
|
|
),
|
|
previous_profile_chunk_len_to_next_name_or_end:
|
|
*previous_profile_chunk_len_to_next_name_or_end,
|
|
}
|
|
},
|
|
)
|
|
.collect(),
|
|
}
|
|
},
|
|
)
|
|
.take(8)
|
|
.collect::<Vec<_>>();
|
|
let name_prelude_candidate_summary = Some(
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidateSummary {
|
|
row_count_with_candidate_window: name_prelude_candidate_rows.len(),
|
|
unique_candidate_pattern_count: name_prelude_candidate_pattern_counts.len(),
|
|
dominant_child_count_candidate: dominant_name_prelude_child_count
|
|
.map(|(value, _)| value),
|
|
dominant_child_count_candidate_count: dominant_name_prelude_child_count
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
dominant_saved_primary_child_byte_candidate: dominant_name_prelude_saved_primary
|
|
.map(|(value, _)| value),
|
|
dominant_saved_primary_child_byte_candidate_hex:
|
|
dominant_name_prelude_saved_primary.map(|(value, _)| format!("0x{value:02x}")),
|
|
dominant_saved_primary_child_byte_candidate_count:
|
|
dominant_name_prelude_saved_primary
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
dominant_candidate_pattern: dominant_name_prelude_candidate_pattern.clone(),
|
|
candidate_pattern_correlations,
|
|
profile_span_correlations,
|
|
compact_prefix_correlations,
|
|
sample_rows: name_prelude_candidate_rows
|
|
.iter()
|
|
.take(8)
|
|
.enumerate()
|
|
.map(
|
|
|(
|
|
sample_index,
|
|
(
|
|
name_tag_relative_offset,
|
|
primary_name,
|
|
secondary_name,
|
|
_prefix_leading_dword,
|
|
_prefix_trailing_word,
|
|
_prefix_separator_byte,
|
|
child_count_candidate,
|
|
saved_primary_child_byte_candidate,
|
|
previous_profile_chunk_len_to_next_name_or_end,
|
|
_previous_short_profile_flag_pair,
|
|
),
|
|
)| {
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidateSample {
|
|
sample_index,
|
|
name_tag_relative_offset: *name_tag_relative_offset,
|
|
primary_name: primary_name.clone(),
|
|
secondary_name: secondary_name.clone(),
|
|
child_count_candidate: *child_count_candidate,
|
|
child_count_candidate_hex: format!("0x{child_count_candidate:04x}"),
|
|
saved_primary_child_byte_candidate:
|
|
*saved_primary_child_byte_candidate,
|
|
saved_primary_child_byte_candidate_hex: format!(
|
|
"0x{saved_primary_child_byte_candidate:02x}"
|
|
),
|
|
previous_profile_chunk_len_to_next_name_or_end:
|
|
*previous_profile_chunk_len_to_next_name_or_end,
|
|
}
|
|
},
|
|
)
|
|
.collect(),
|
|
},
|
|
);
|
|
let dominant_profile_span_class_summary = dominant_profile_chunk_len
|
|
.map(|(dominant_profile_span_len, _)| {
|
|
let dominant_rows = embedded_name_rows
|
|
.iter()
|
|
.zip(payload_envelope_rows.iter())
|
|
.filter_map(|(name_row, envelope_row)| {
|
|
(envelope_row.profile_chunk_len_to_next_name_or_end
|
|
== Some(dominant_profile_span_len))
|
|
.then(|| {
|
|
let candidate_offset =
|
|
name_row.name_tag_relative_offset.checked_sub(3);
|
|
let child_count_candidate = candidate_offset
|
|
.and_then(|offset| read_u16_at(records_payload, offset));
|
|
let saved_primary_child_byte_candidate = candidate_offset
|
|
.and_then(|offset| read_u8_at(records_payload, offset + 2));
|
|
(
|
|
name_row.name_tag_relative_offset,
|
|
name_row.primary_name.clone(),
|
|
name_row.secondary_name.clone(),
|
|
name_row.prefix_leading_dword,
|
|
name_row.prefix_trailing_word,
|
|
name_row.prefix_separator_byte,
|
|
child_count_candidate,
|
|
saved_primary_child_byte_candidate,
|
|
)
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let mut dominant_name_pair_counts =
|
|
BTreeMap::<(Option<String>, Option<String>), usize>::new();
|
|
let mut dominant_prefix_counts = BTreeMap::<(u32, u16, u8), usize>::new();
|
|
let mut dominant_candidate_pattern_counts = BTreeMap::<(u16, u8), usize>::new();
|
|
for (
|
|
_,
|
|
primary_name,
|
|
secondary_name,
|
|
prefix_leading_dword,
|
|
prefix_trailing_word,
|
|
prefix_separator_byte,
|
|
child_count_candidate,
|
|
saved_primary_child_byte_candidate,
|
|
) in &dominant_rows
|
|
{
|
|
*dominant_name_pair_counts
|
|
.entry((primary_name.clone(), secondary_name.clone()))
|
|
.or_default() += 1;
|
|
*dominant_prefix_counts
|
|
.entry((
|
|
*prefix_leading_dword,
|
|
*prefix_trailing_word,
|
|
*prefix_separator_byte,
|
|
))
|
|
.or_default() += 1;
|
|
if let (Some(child_count_candidate), Some(saved_primary_child_byte_candidate)) =
|
|
(child_count_candidate, saved_primary_child_byte_candidate)
|
|
{
|
|
*dominant_candidate_pattern_counts
|
|
.entry((*child_count_candidate, *saved_primary_child_byte_candidate))
|
|
.or_default() += 1;
|
|
}
|
|
}
|
|
let dominant_name_pair = dominant_name_pair_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|((primary_name, secondary_name), count)| {
|
|
(primary_name.clone(), secondary_name.clone(), *count)
|
|
});
|
|
let dominant_prefix = dominant_prefix_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(
|
|
|((prefix_leading_dword, prefix_trailing_word, prefix_separator_byte), count)| {
|
|
(
|
|
*prefix_leading_dword,
|
|
*prefix_trailing_word,
|
|
*prefix_separator_byte,
|
|
*count,
|
|
)
|
|
},
|
|
);
|
|
let dominant_candidate_pattern = dominant_candidate_pattern_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(
|
|
|((child_count_candidate, saved_primary_child_byte_candidate), count)| {
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern {
|
|
child_count_candidate: *child_count_candidate,
|
|
child_count_candidate_hex: format!(
|
|
"0x{child_count_candidate:04x}"
|
|
),
|
|
saved_primary_child_byte_candidate:
|
|
*saved_primary_child_byte_candidate,
|
|
saved_primary_child_byte_candidate_hex: format!(
|
|
"0x{saved_primary_child_byte_candidate:02x}"
|
|
),
|
|
count: *count,
|
|
}
|
|
},
|
|
);
|
|
let name_pair_summaries = dominant_name_pair_counts
|
|
.iter()
|
|
.map(|((primary_name, secondary_name), count)| {
|
|
SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanNamePairSummary {
|
|
primary_name: primary_name.clone(),
|
|
secondary_name: secondary_name.clone(),
|
|
count: *count,
|
|
}
|
|
})
|
|
.take(8)
|
|
.collect::<Vec<_>>();
|
|
let compact_prefix_pattern_summaries = dominant_prefix_counts
|
|
.iter()
|
|
.map(
|
|
|((prefix_leading_dword, prefix_trailing_word, prefix_separator_byte), count)| {
|
|
SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanPrefixSummary {
|
|
prefix_leading_dword: *prefix_leading_dword,
|
|
prefix_leading_dword_hex: format!(
|
|
"0x{prefix_leading_dword:08x}"
|
|
),
|
|
prefix_trailing_word: *prefix_trailing_word,
|
|
prefix_trailing_word_hex: format!(
|
|
"0x{prefix_trailing_word:04x}"
|
|
),
|
|
prefix_separator_byte: *prefix_separator_byte,
|
|
prefix_separator_byte_hex: format!(
|
|
"0x{prefix_separator_byte:02x}"
|
|
),
|
|
count: *count,
|
|
}
|
|
},
|
|
)
|
|
.take(8)
|
|
.collect::<Vec<_>>();
|
|
let candidate_pattern_summaries = dominant_candidate_pattern_counts
|
|
.iter()
|
|
.map(
|
|
|((child_count_candidate, saved_primary_child_byte_candidate), count)| {
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern {
|
|
child_count_candidate: *child_count_candidate,
|
|
child_count_candidate_hex: format!(
|
|
"0x{child_count_candidate:04x}"
|
|
),
|
|
saved_primary_child_byte_candidate:
|
|
*saved_primary_child_byte_candidate,
|
|
saved_primary_child_byte_candidate_hex: format!(
|
|
"0x{saved_primary_child_byte_candidate:02x}"
|
|
),
|
|
count: *count,
|
|
}
|
|
},
|
|
)
|
|
.take(8)
|
|
.collect::<Vec<_>>();
|
|
SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanClassSummary {
|
|
profile_chunk_len_to_next_name_or_end: dominant_profile_span_len,
|
|
row_count: dominant_rows.len(),
|
|
unique_name_pair_count: dominant_name_pair_counts.len(),
|
|
unique_compact_prefix_pattern_count: dominant_prefix_counts.len(),
|
|
dominant_candidate_pattern,
|
|
dominant_primary_name: dominant_name_pair
|
|
.as_ref()
|
|
.and_then(|(primary_name, _, _)| primary_name.clone()),
|
|
dominant_secondary_name: dominant_name_pair
|
|
.as_ref()
|
|
.and_then(|(_, secondary_name, _)| secondary_name.clone()),
|
|
dominant_name_pair_count: dominant_name_pair
|
|
.map(|(_, _, count)| count)
|
|
.unwrap_or_default(),
|
|
dominant_prefix_leading_dword: dominant_prefix
|
|
.map(|(prefix_leading_dword, _, _, _)| prefix_leading_dword),
|
|
dominant_prefix_leading_dword_hex: dominant_prefix.map(
|
|
|(prefix_leading_dword, _, _, _)| format!("0x{prefix_leading_dword:08x}"),
|
|
),
|
|
dominant_prefix_trailing_word: dominant_prefix
|
|
.map(|(_, prefix_trailing_word, _, _)| prefix_trailing_word),
|
|
dominant_prefix_trailing_word_hex: dominant_prefix.map(
|
|
|(_, prefix_trailing_word, _, _)| format!("0x{prefix_trailing_word:04x}"),
|
|
),
|
|
dominant_prefix_separator_byte: dominant_prefix
|
|
.map(|(_, _, prefix_separator_byte, _)| prefix_separator_byte),
|
|
dominant_prefix_separator_byte_hex: dominant_prefix.map(
|
|
|(_, _, prefix_separator_byte, _)| format!("0x{prefix_separator_byte:02x}"),
|
|
),
|
|
dominant_prefix_count: dominant_prefix
|
|
.map(|(_, _, _, count)| count)
|
|
.unwrap_or_default(),
|
|
sample_rows: dominant_rows
|
|
.iter()
|
|
.take(8)
|
|
.enumerate()
|
|
.map(
|
|
|(
|
|
sample_index,
|
|
(
|
|
name_tag_relative_offset,
|
|
primary_name,
|
|
secondary_name,
|
|
prefix_leading_dword,
|
|
prefix_trailing_word,
|
|
prefix_separator_byte,
|
|
child_count_candidate,
|
|
saved_primary_child_byte_candidate,
|
|
),
|
|
)| {
|
|
SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanSample {
|
|
sample_index,
|
|
name_tag_relative_offset: *name_tag_relative_offset,
|
|
primary_name: primary_name.clone(),
|
|
secondary_name: secondary_name.clone(),
|
|
prefix_leading_dword: *prefix_leading_dword,
|
|
prefix_leading_dword_hex: format!(
|
|
"0x{prefix_leading_dword:08x}"
|
|
),
|
|
prefix_trailing_word: *prefix_trailing_word,
|
|
prefix_trailing_word_hex: format!(
|
|
"0x{prefix_trailing_word:04x}"
|
|
),
|
|
prefix_separator_byte: *prefix_separator_byte,
|
|
prefix_separator_byte_hex: format!(
|
|
"0x{prefix_separator_byte:02x}"
|
|
),
|
|
child_count_candidate: *child_count_candidate,
|
|
child_count_candidate_hex: child_count_candidate
|
|
.map(|value| format!("0x{value:04x}")),
|
|
saved_primary_child_byte_candidate:
|
|
*saved_primary_child_byte_candidate,
|
|
saved_primary_child_byte_candidate_hex:
|
|
saved_primary_child_byte_candidate
|
|
.map(|value| format!("0x{value:02x}")),
|
|
}
|
|
},
|
|
)
|
|
.collect(),
|
|
name_pair_summaries,
|
|
compact_prefix_pattern_summaries,
|
|
candidate_pattern_summaries,
|
|
}
|
|
});
|
|
let payload_envelope_summary = Some(
|
|
SmpSavePlacedStructureDynamicSideBufferPayloadEnvelopeSummary {
|
|
row_count_with_policy_tag_before_next_name,
|
|
row_count_with_complete_0x55f1_0x55f2_0x55f3_envelope,
|
|
row_count_missing_policy_tag_before_next_name,
|
|
row_count_missing_profile_tag_after_policy,
|
|
unique_policy_chunk_lens,
|
|
unique_profile_chunk_lens,
|
|
dominant_policy_chunk_len: dominant_policy_chunk_len.map(|(len, _)| len),
|
|
dominant_policy_chunk_len_count: dominant_policy_chunk_len
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
dominant_profile_chunk_len: dominant_profile_chunk_len.map(|(len, _)| len),
|
|
dominant_profile_chunk_len_count: dominant_profile_chunk_len
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
short_profile_flag_pair_summary: short_profile_flag_pair_summary.clone(),
|
|
fixed_policy_summary: fixed_policy_summary.clone(),
|
|
name_prelude_candidate_summary: name_prelude_candidate_summary.clone(),
|
|
dominant_profile_span_class_summary: dominant_profile_span_class_summary.clone(),
|
|
sample_rows: payload_envelope_rows
|
|
.iter()
|
|
.take(8)
|
|
.enumerate()
|
|
.map(|(sample_index, row)| {
|
|
SmpSavePlacedStructureDynamicSideBufferPayloadEnvelopeSample {
|
|
sample_index,
|
|
name_tag_relative_offset: row.name_tag_relative_offset,
|
|
primary_name: row.primary_name.clone(),
|
|
secondary_name: row.secondary_name.clone(),
|
|
name_payload_end_relative_offset: row.name_payload_end_relative_offset,
|
|
policy_tag_relative_offset: row.policy_tag_relative_offset,
|
|
profile_tag_relative_offset: row.profile_tag_relative_offset,
|
|
next_name_tag_relative_offset: row.next_name_tag_relative_offset,
|
|
name_to_policy_gap_len: row.name_to_policy_gap_len,
|
|
policy_chunk_len: row.policy_chunk_len,
|
|
profile_chunk_len_to_next_name_or_end: row
|
|
.profile_chunk_len_to_next_name_or_end,
|
|
}
|
|
})
|
|
.collect(),
|
|
},
|
|
);
|
|
let bitset_len = ((usize::try_from(summary.live_id_bound).ok()?).saturating_add(8)) / 8;
|
|
let live_entry_prelude_summary = payload
|
|
.get(
|
|
INDEXED_COLLECTION_SERIALIZED_HEADER_LEN
|
|
..INDEXED_COLLECTION_SERIALIZED_HEADER_LEN + bitset_len,
|
|
)
|
|
.and_then(|bitset| {
|
|
let live_entry_ids =
|
|
decode_live_entry_ids_from_tombstone_bitset(bitset, summary.live_id_bound)?;
|
|
if live_entry_ids.len() != usize::try_from(summary.live_record_count).ok()? {
|
|
return None;
|
|
}
|
|
#[derive(Clone)]
|
|
struct LiveEntryPreludeRow {
|
|
live_entry_id: u32,
|
|
payload_relative_offset: usize,
|
|
child_count: u16,
|
|
saved_primary_child_byte: u8,
|
|
first_payload_dword: u32,
|
|
first_name_tag_relative_offset: Option<usize>,
|
|
first_primary_name: Option<String>,
|
|
first_secondary_name: Option<String>,
|
|
first_tertiary_name: Option<String>,
|
|
}
|
|
let mut rows = Vec::new();
|
|
let mut cursor = 0usize;
|
|
for live_entry_id in live_entry_ids.iter().copied() {
|
|
let payload_len = usize::from(read_u16_at(records_payload, cursor)?);
|
|
let payload_relative_offset = cursor.checked_add(2)?;
|
|
let payload_end = payload_relative_offset.checked_add(payload_len)?;
|
|
let payload_bytes =
|
|
records_payload.get(payload_relative_offset..payload_end)?;
|
|
let child_count = read_u16_at(payload_bytes, 0)?;
|
|
let saved_primary_child_byte = read_u8_at(payload_bytes, 2)?;
|
|
let first_payload_dword = read_u32_at(payload_bytes, 0)?;
|
|
let first_name_tag_relative_offset = (0usize..=16usize).find(|offset| {
|
|
read_u16_at(payload_bytes, *offset) == Some(SAVE_REGION_RECORD_NAME_TAG)
|
|
});
|
|
let (first_primary_name, first_secondary_name, first_tertiary_name) =
|
|
first_name_tag_relative_offset
|
|
.and_then(|relative_offset| {
|
|
let name_payload =
|
|
payload_bytes.get(relative_offset.checked_add(4)?..)?;
|
|
parse_save_len_prefixed_ascii_name_triplet(name_payload)
|
|
})
|
|
.unwrap_or_default();
|
|
rows.push(LiveEntryPreludeRow {
|
|
live_entry_id,
|
|
payload_relative_offset,
|
|
child_count,
|
|
saved_primary_child_byte,
|
|
first_payload_dword,
|
|
first_name_tag_relative_offset,
|
|
first_primary_name: (!first_primary_name.is_empty())
|
|
.then_some(first_primary_name),
|
|
first_secondary_name: (!first_secondary_name.is_empty())
|
|
.then_some(first_secondary_name),
|
|
first_tertiary_name,
|
|
});
|
|
cursor = payload_end;
|
|
}
|
|
let payload_relative_offset_monotonic = rows.windows(2).all(|window| {
|
|
window[0].payload_relative_offset < window[1].payload_relative_offset
|
|
});
|
|
let mut child_count_counts = BTreeMap::<u16, usize>::new();
|
|
let mut saved_primary_child_byte_counts = BTreeMap::<u8, usize>::new();
|
|
let mut first_name_tag_relative_offset_counts = BTreeMap::<usize, usize>::new();
|
|
for row in &rows {
|
|
*child_count_counts.entry(row.child_count).or_default() += 1;
|
|
*saved_primary_child_byte_counts
|
|
.entry(row.saved_primary_child_byte)
|
|
.or_default() += 1;
|
|
if let Some(first_name_tag_relative_offset) = row.first_name_tag_relative_offset
|
|
{
|
|
*first_name_tag_relative_offset_counts
|
|
.entry(first_name_tag_relative_offset)
|
|
.or_default() += 1;
|
|
}
|
|
}
|
|
let dominant_child_count = child_count_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|(value, count)| (*value, *count));
|
|
let dominant_saved_primary_child_byte = saved_primary_child_byte_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|(value, count)| (*value, *count));
|
|
let dominant_first_name_tag_relative_offset = first_name_tag_relative_offset_counts
|
|
.iter()
|
|
.max_by(|(left_key, left_count), (right_key, right_count)| {
|
|
left_count
|
|
.cmp(right_count)
|
|
.then_with(|| right_key.cmp(left_key))
|
|
})
|
|
.map(|(value, count)| (*value, *count));
|
|
Some(
|
|
SmpSavePlacedStructureDynamicSideBufferLiveEntryPreludeSummary {
|
|
live_entry_directory_row_count: rows.len(),
|
|
decoded_live_entry_id_count: live_entry_ids.len(),
|
|
payload_relative_offset_monotonic,
|
|
rows_with_payload_pointer_inside_records_span: rows.len(),
|
|
rows_with_zero_child_count: rows
|
|
.iter()
|
|
.filter(|row| row.child_count == 0)
|
|
.count(),
|
|
rows_with_nonzero_child_count: rows
|
|
.iter()
|
|
.filter(|row| row.child_count != 0)
|
|
.count(),
|
|
rows_with_first_name_tag_after_prelude: rows
|
|
.iter()
|
|
.filter(|row| row.first_name_tag_relative_offset.is_some())
|
|
.count(),
|
|
rows_with_first_name_tag_at_offset_3: rows
|
|
.iter()
|
|
.filter(|row| row.first_name_tag_relative_offset == Some(3))
|
|
.count(),
|
|
unique_child_count_values: child_count_counts.keys().copied().collect(),
|
|
unique_first_name_tag_relative_offsets:
|
|
first_name_tag_relative_offset_counts
|
|
.keys()
|
|
.copied()
|
|
.collect(),
|
|
dominant_child_count: dominant_child_count.map(|(value, _)| value),
|
|
dominant_child_count_count: dominant_child_count
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
dominant_saved_primary_child_byte: dominant_saved_primary_child_byte
|
|
.map(|(value, _)| value),
|
|
dominant_saved_primary_child_byte_hex: dominant_saved_primary_child_byte
|
|
.map(|(value, _)| format!("0x{value:02x}")),
|
|
dominant_saved_primary_child_byte_count: dominant_saved_primary_child_byte
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
dominant_first_name_tag_relative_offset:
|
|
dominant_first_name_tag_relative_offset.map(|(value, _)| value),
|
|
dominant_first_name_tag_relative_offset_count:
|
|
dominant_first_name_tag_relative_offset
|
|
.map(|(_, count)| count)
|
|
.unwrap_or_default(),
|
|
sample_rows: rows
|
|
.iter()
|
|
.take(8)
|
|
.enumerate()
|
|
.map(|(sample_index, row)| {
|
|
SmpSavePlacedStructureDynamicSideBufferLiveEntryPreludeSample {
|
|
sample_index,
|
|
live_entry_id: row.live_entry_id,
|
|
payload_relative_offset: row.payload_relative_offset as u32,
|
|
payload_relative_offset_hex: format!(
|
|
"0x{:08x}",
|
|
row.payload_relative_offset
|
|
),
|
|
payload_relative_to_records: row.payload_relative_offset,
|
|
child_count: row.child_count,
|
|
child_count_hex: format!("0x{:04x}", row.child_count),
|
|
saved_primary_child_byte: row.saved_primary_child_byte,
|
|
saved_primary_child_byte_hex: format!(
|
|
"0x{:02x}",
|
|
row.saved_primary_child_byte
|
|
),
|
|
first_payload_dword_hex: format!(
|
|
"0x{:08x}",
|
|
row.first_payload_dword
|
|
),
|
|
first_name_tag_relative_offset: row
|
|
.first_name_tag_relative_offset,
|
|
first_primary_name: row.first_primary_name.clone(),
|
|
first_secondary_name: row.first_secondary_name.clone(),
|
|
first_tertiary_name: row.first_tertiary_name.clone(),
|
|
}
|
|
})
|
|
.collect(),
|
|
},
|
|
)
|
|
});
|
|
let unique_embedded_name_pair_count = name_pair_summaries.len();
|
|
let dominant_compact_prefix_pattern = compact_prefix_pattern_summaries.first().cloned();
|
|
let decoded_embedded_name_row_count = embedded_name_rows
|
|
.iter()
|
|
.filter(|row| row.primary_name.is_some() && row.secondary_name.is_some())
|
|
.count();
|
|
let decoded_embedded_name_row_with_tertiary_name_count = embedded_name_rows
|
|
.iter()
|
|
.filter(|row| {
|
|
row.primary_name.is_some()
|
|
&& row.secondary_name.is_some()
|
|
&& row.tertiary_name.is_some()
|
|
})
|
|
.count();
|
|
return Some(SmpSavePlacedStructureDynamicSideBufferProbe {
|
|
profile_family: profile.profile_family.clone(),
|
|
source_kind: "save-placed-structure-dynamic-side-buffer-records".to_string(),
|
|
semantic_family: "scenario-save-placed-structure-dynamic-side-buffer-records".to_string(),
|
|
metadata_tag_offset,
|
|
records_tag_offset,
|
|
close_tag_offset,
|
|
records_span_len: close_tag_offset.saturating_sub(records_tag_offset + 4),
|
|
direct_record_stride: summary.direct_record_stride,
|
|
direct_record_stride_hex: format!("0x{:08x}", summary.direct_record_stride),
|
|
live_id_bound: summary.live_id_bound,
|
|
live_id_bound_hex: format!("0x{:08x}", summary.live_id_bound),
|
|
live_record_count: summary.live_record_count,
|
|
live_record_count_hex: format!("0x{:08x}", summary.live_record_count),
|
|
owner_shared_dword,
|
|
owner_shared_dword_hex: format!("0x{owner_shared_dword:08x}"),
|
|
owner_shared_dword_relative_offset,
|
|
owner_shared_dword_matches_first_compact_prefix_leading_dword:
|
|
owner_shared_dword == prefix_leading_dword,
|
|
first_record_child_count_after_owner_shared,
|
|
first_record_child_count_after_owner_shared_hex: first_record_child_count_after_owner_shared
|
|
.map(|value| format!("0x{value:04x}")),
|
|
first_record_saved_primary_child_byte_after_owner_shared,
|
|
first_record_saved_primary_child_byte_after_owner_shared_hex:
|
|
first_record_saved_primary_child_byte_after_owner_shared
|
|
.map(|value| format!("0x{value:02x}")),
|
|
first_record_first_name_tag_relative_offset_after_owner_shared,
|
|
prefix_leading_dword,
|
|
prefix_leading_dword_hex: format!("0x{prefix_leading_dword:08x}"),
|
|
prefix_trailing_word,
|
|
prefix_trailing_word_hex: format!("0x{prefix_trailing_word:04x}"),
|
|
prefix_separator_byte,
|
|
prefix_separator_byte_hex: format!("0x{prefix_separator_byte:02x}"),
|
|
first_embedded_name_tag_relative_offset,
|
|
embedded_name_tag_count: embedded_name_tag_offsets.len(),
|
|
decoded_embedded_name_row_count,
|
|
decoded_embedded_name_row_with_tertiary_name_count,
|
|
unique_compact_prefix_pattern_count: compact_prefix_pattern_summaries.len(),
|
|
prefix_leading_dword_matching_embedded_profile_tag_count,
|
|
unique_embedded_name_pair_count,
|
|
first_embedded_primary_name: Some(first_embedded_primary_name.clone()),
|
|
first_embedded_secondary_name: Some(first_embedded_secondary_name.clone()),
|
|
first_embedded_tertiary_name: first_embedded_tertiary_name.clone(),
|
|
embedded_name_row_samples,
|
|
compact_prefix_pattern_summaries,
|
|
name_pair_summaries,
|
|
payload_envelope_summary,
|
|
live_entry_prelude_summary: live_entry_prelude_summary.clone(),
|
|
evidence: vec![
|
|
"exact little-endian u32 tag family 0x38a5/0x38a6/0x38a7 appears as a separate save-side tagged collection on grounded saves".to_string(),
|
|
format!(
|
|
"direct disassembly now shows 0x00493be0 consuming shared owner-local dword 0x{owner_shared_dword:08x} from the 0x38a6 stream before iterating live infrastructure records"
|
|
),
|
|
format!(
|
|
"grounded 0x38a6 record stream then starts the first infrastructure payload with child_count={}, saved_primary_child_byte={}, and first 0x55f1 at relative offset {:?} after that shared dword",
|
|
first_record_child_count_after_owner_shared.unwrap_or_default(),
|
|
first_record_saved_primary_child_byte_after_owner_shared
|
|
.map(|value| format!("0x{value:02x}"))
|
|
.as_deref()
|
|
.unwrap_or("0x00"),
|
|
first_record_first_name_tag_relative_offset_after_owner_shared
|
|
),
|
|
"records payload begins with a compact 6-byte prefix plus one separator byte before the first embedded 0x55f1 name row".to_string(),
|
|
"first embedded 0x55f1 row decodes with placed-structure-style dual names, which makes this the strongest current candidate for the separate placed-structure dynamic side-buffer owner seam".to_string(),
|
|
format!(
|
|
"grounded first embedded names are {:?}/{:?}/{:?} with {} embedded 0x55f1 name rows in the tagged records span",
|
|
Some(first_embedded_primary_name),
|
|
Some(first_embedded_secondary_name),
|
|
first_embedded_tertiary_name,
|
|
embedded_name_tag_offsets.len()
|
|
),
|
|
format!(
|
|
"{} of {} embedded name rows use compact leading dword 0x{:08x}, matching the placed-structure embedded profile tag",
|
|
prefix_leading_dword_matching_embedded_profile_tag_count,
|
|
embedded_name_rows.len(),
|
|
u32::from(SAVE_REGION_RECORD_PROFILE_TAG)
|
|
),
|
|
format!(
|
|
"{decoded_embedded_name_row_count} decoded embedded name rows collapse into {} unique placed-structure name pairs; {} rows also expose a third embedded 0x55f1 string",
|
|
unique_embedded_name_pair_count
|
|
,
|
|
decoded_embedded_name_row_with_tertiary_name_count
|
|
),
|
|
format!(
|
|
"{} of {} embedded 0x55f1 rows currently have a complete 0x55f1/0x55f2/0x55f3 envelope before the next name row; {} rows are still missing 0x55f2 and {} rows have 0x55f2 without a later 0x55f3",
|
|
row_count_with_complete_0x55f1_0x55f2_0x55f3_envelope,
|
|
embedded_name_rows.len(),
|
|
row_count_missing_policy_tag_before_next_name,
|
|
row_count_missing_profile_tag_after_policy
|
|
),
|
|
dominant_policy_chunk_len
|
|
.map(|(policy_chunk_len, count)| {
|
|
format!(
|
|
"dominant embedded 0x55f2 policy chunk length is 0x{policy_chunk_len:x} bytes across {count} rows"
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no dominant embedded 0x55f2 policy chunk length was available"
|
|
.to_string()
|
|
}),
|
|
dominant_profile_chunk_len
|
|
.map(|(profile_chunk_len, count)| {
|
|
format!(
|
|
"dominant embedded 0x55f3 payload-to-next-name span is 0x{profile_chunk_len:x} bytes across {count} rows"
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no dominant embedded 0x55f3 payload-to-next-name span was available"
|
|
.to_string()
|
|
}),
|
|
short_profile_flag_pair_summary
|
|
.as_ref()
|
|
.and_then(|summary| summary.dominant_flag_pair.as_ref())
|
|
.map(|pair| {
|
|
format!(
|
|
"direct disassembly now bounds the short trailing lane through 0x52ebd0/0x52ec50 as two serialized flag bytes folded into [this+0x20] bits 0x20/0x40; grounded 0x06-byte rows currently favor {}/{} across {} rows ({} rows total with the short span)",
|
|
pair.first_flag_byte_hex,
|
|
pair.second_flag_byte_hex,
|
|
pair.count,
|
|
short_profile_flag_pair_summary
|
|
.as_ref()
|
|
.map(|summary| summary.row_count_with_0x06_profile_span)
|
|
.unwrap_or_default()
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no dominant short trailing flag-byte pair was available".to_string()
|
|
}),
|
|
fixed_policy_summary
|
|
.as_ref()
|
|
.map(|summary| {
|
|
format!(
|
|
"direct disassembly now bounds the fixed 0x55f2 lane through 0x455870/0x455930 as six serialized dwords plus one trailing u16; grounded rows currently keep trailing word {} across {} of {} fixed-policy rows",
|
|
summary
|
|
.dominant_trailing_word_hex
|
|
.as_deref()
|
|
.unwrap_or("0x0000"),
|
|
summary.dominant_trailing_word_count,
|
|
summary.row_count_with_0x1a_policy_chunk
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no fixed 0x55f2 policy summary was available".to_string()
|
|
}),
|
|
live_entry_prelude_summary
|
|
.as_ref()
|
|
.map(|summary| {
|
|
format!(
|
|
"decoded {} live-entry directory rows with payload pointers inside the records span; dominant child count={} x{}, dominant saved primary-child byte={} x{}, dominant first 0x55f1 offset={} x{}, and {} rows start the first child callback immediately at payload +0x3",
|
|
summary.rows_with_payload_pointer_inside_records_span,
|
|
summary.dominant_child_count.unwrap_or_default(),
|
|
summary.dominant_child_count_count,
|
|
summary
|
|
.dominant_saved_primary_child_byte_hex
|
|
.as_deref()
|
|
.unwrap_or("0x00"),
|
|
summary.dominant_saved_primary_child_byte_count,
|
|
summary
|
|
.dominant_first_name_tag_relative_offset
|
|
.unwrap_or_default(),
|
|
summary.dominant_first_name_tag_relative_offset_count,
|
|
summary.rows_with_first_name_tag_at_offset_3
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no live-entry prelude summary was available".to_string()
|
|
}),
|
|
dominant_compact_prefix_pattern
|
|
.map(|pattern| {
|
|
format!(
|
|
"dominant compact prefix pattern {} / {} / {} occurs {} times; section-like rows={}, cap-like rows={}, first names={:?}/{:?}",
|
|
pattern.prefix_leading_dword_hex,
|
|
pattern.prefix_trailing_word_hex,
|
|
pattern.prefix_separator_byte_hex,
|
|
pattern.count,
|
|
pattern.section_like_primary_name_count,
|
|
pattern.cap_like_primary_name_count,
|
|
pattern.first_primary_name,
|
|
pattern.first_secondary_name
|
|
)
|
|
})
|
|
.unwrap_or_else(|| {
|
|
"no dominant compact prefix pattern summary was available".to_string()
|
|
}),
|
|
],
|
|
});
|
|
}
|
|
None
|
|
}
|
|
|
|
fn summarize_placed_structure_dynamic_side_buffer_alignment(
|
|
side_buffer: &SmpSavePlacedStructureDynamicSideBufferProbe,
|
|
triplets: &SmpSavePlacedStructureRecordTripletProbe,
|
|
) -> SmpSavePlacedStructureDynamicSideBufferAlignmentProbe {
|
|
let triplet_name_pairs = triplets
|
|
.entries
|
|
.iter()
|
|
.map(|entry| (entry.primary_name.clone(), entry.secondary_name.clone()))
|
|
.collect::<BTreeSet<_>>();
|
|
let matched_name_pair_samples = side_buffer
|
|
.name_pair_summaries
|
|
.iter()
|
|
.filter(|summary| {
|
|
triplet_name_pairs
|
|
.contains(&(summary.primary_name.clone(), summary.secondary_name.clone()))
|
|
})
|
|
.take(5)
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
let unmatched_side_buffer_name_pair_samples = side_buffer
|
|
.name_pair_summaries
|
|
.iter()
|
|
.filter(|summary| {
|
|
!triplet_name_pairs
|
|
.contains(&(summary.primary_name.clone(), summary.secondary_name.clone()))
|
|
})
|
|
.take(5)
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
let side_buffer_rows_with_matching_triplet_name_pair_count = side_buffer
|
|
.name_pair_summaries
|
|
.iter()
|
|
.filter(|summary| {
|
|
triplet_name_pairs
|
|
.contains(&(summary.primary_name.clone(), summary.secondary_name.clone()))
|
|
})
|
|
.map(|summary| summary.count)
|
|
.sum::<usize>();
|
|
let unique_side_buffer_name_pair_count = side_buffer.name_pair_summaries.len();
|
|
let overlapping_name_pair_count = side_buffer
|
|
.name_pair_summaries
|
|
.iter()
|
|
.filter(|summary| {
|
|
triplet_name_pairs
|
|
.contains(&(summary.primary_name.clone(), summary.secondary_name.clone()))
|
|
})
|
|
.count();
|
|
SmpSavePlacedStructureDynamicSideBufferAlignmentProbe {
|
|
unique_side_buffer_name_pair_count,
|
|
unique_triplet_name_pair_count: triplet_name_pairs.len(),
|
|
overlapping_name_pair_count,
|
|
side_buffer_row_count: side_buffer.decoded_embedded_name_row_count,
|
|
side_buffer_rows_with_matching_triplet_name_pair_count,
|
|
side_buffer_rows_without_matching_triplet_name_pair_count: side_buffer
|
|
.decoded_embedded_name_row_count
|
|
.saturating_sub(side_buffer_rows_with_matching_triplet_name_pair_count),
|
|
triplet_name_pairs_without_side_buffer_match_count: triplet_name_pairs
|
|
.len()
|
|
.saturating_sub(overlapping_name_pair_count),
|
|
matched_name_pair_samples,
|
|
unmatched_side_buffer_name_pair_samples,
|
|
evidence: vec![
|
|
"placed-structure dynamic side-buffer alignment compares decoded 0x38a5 embedded name pairs against the grounded 0x36b1 triplet name-pair corpus".to_string(),
|
|
format!(
|
|
"{} of {} decoded side-buffer rows currently reuse name pairs already present in the placed-structure triplet owner seam",
|
|
side_buffer_rows_with_matching_triplet_name_pair_count,
|
|
side_buffer.decoded_embedded_name_row_count
|
|
),
|
|
format!(
|
|
"{} of {} unique side-buffer name pairs overlap the grounded triplet name-pair corpus",
|
|
overlapping_name_pair_count,
|
|
unique_side_buffer_name_pair_count
|
|
),
|
|
],
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy)]
|
|
struct IndexedCollectionHeaderSummary {
|
|
metadata_tag_offset: usize,
|
|
records_tag_offset: usize,
|
|
close_tag_offset: usize,
|
|
direct_collection_flag: u32,
|
|
direct_record_stride: u32,
|
|
live_id_bound: u32,
|
|
live_record_count: u32,
|
|
header_words: [u32; INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT],
|
|
}
|
|
|
|
fn parse_save_tagged_collection_header_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
metadata_tag: u32,
|
|
records_tag: u32,
|
|
close_tag: u32,
|
|
source_kind: &str,
|
|
semantic_family: &str,
|
|
predicate: impl Fn(IndexedCollectionHeaderSummary) -> bool,
|
|
mut evidence: Vec<String>,
|
|
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
|
|
if file_extension_hint != Some("gms") {
|
|
return None;
|
|
}
|
|
let profile = container_profile?;
|
|
if !matches!(
|
|
profile.profile_family.as_str(),
|
|
"rt3-classic-save-container-v1"
|
|
| "rt3-105-save-container-v1"
|
|
| "rt3-105-scenario-save-container-v1"
|
|
| "rt3-105-alt-save-container-v1"
|
|
) {
|
|
return None;
|
|
}
|
|
|
|
let metadata_offsets = find_u32_le_offsets(bytes, metadata_tag);
|
|
let records_offsets = find_u32_le_offsets(bytes, records_tag);
|
|
let close_offsets = find_u32_le_offsets(bytes, close_tag);
|
|
|
|
let summary = metadata_offsets
|
|
.into_iter()
|
|
.filter_map(|metadata_tag_offset| {
|
|
let records_tag_offset = records_offsets
|
|
.iter()
|
|
.copied()
|
|
.find(|offset| *offset > metadata_tag_offset)?;
|
|
let close_tag_offset = close_offsets
|
|
.iter()
|
|
.copied()
|
|
.find(|offset| *offset > records_tag_offset)?;
|
|
let payload = bytes.get(metadata_tag_offset + 4..records_tag_offset)?;
|
|
if payload.len() < INDEXED_COLLECTION_SERIALIZED_HEADER_LEN {
|
|
return None;
|
|
}
|
|
|
|
let header_words = (0..INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT)
|
|
.map(|index| read_u32_at(payload, index * 4))
|
|
.collect::<Option<Vec<_>>>()?;
|
|
let header_words: [u32; INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT] =
|
|
header_words.try_into().ok()?;
|
|
let summary = IndexedCollectionHeaderSummary {
|
|
metadata_tag_offset,
|
|
records_tag_offset,
|
|
close_tag_offset,
|
|
direct_collection_flag: header_words[0],
|
|
direct_record_stride: header_words[1],
|
|
live_id_bound: header_words[4],
|
|
live_record_count: header_words[5],
|
|
header_words,
|
|
};
|
|
predicate(summary).then_some(summary)
|
|
})
|
|
.next()?;
|
|
|
|
evidence.push(format!(
|
|
"exact little-endian u32 tag family 0x{metadata_tag:04x}/0x{records_tag:04x}/0x{close_tag:04x} appears at file offsets 0x{:x}/0x{:x}/0x{:x}",
|
|
summary.metadata_tag_offset, summary.records_tag_offset, summary.close_tag_offset
|
|
));
|
|
evidence.push(format!(
|
|
"header words report direct_collection_flag={}, direct_record_stride=0x{:x}, live_id_bound={}, live_record_count={}",
|
|
summary.direct_collection_flag,
|
|
summary.direct_record_stride,
|
|
summary.live_id_bound,
|
|
summary.live_record_count
|
|
));
|
|
|
|
Some(SmpSaveTaggedCollectionHeaderProbe {
|
|
profile_family: profile.profile_family.clone(),
|
|
source_kind: source_kind.to_string(),
|
|
semantic_family: semantic_family.to_string(),
|
|
metadata_tag_offset: summary.metadata_tag_offset,
|
|
records_tag_offset: summary.records_tag_offset,
|
|
close_tag_offset: summary.close_tag_offset,
|
|
direct_collection_flag: summary.direct_collection_flag,
|
|
direct_collection_flag_hex: format!("0x{:08x}", summary.direct_collection_flag),
|
|
direct_record_stride: summary.direct_record_stride,
|
|
direct_record_stride_hex: format!("0x{:08x}", summary.direct_record_stride),
|
|
live_id_bound: summary.live_id_bound,
|
|
live_id_bound_hex: format!("0x{:08x}", summary.live_id_bound),
|
|
live_record_count: summary.live_record_count,
|
|
live_record_count_hex: format!("0x{:08x}", summary.live_record_count),
|
|
header_words: summary.header_words.to_vec(),
|
|
header_hex_words: summary
|
|
.header_words
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect(),
|
|
evidence,
|
|
})
|
|
}
|
|
|
|
fn scan_save_unclassified_tagged_collection_header_probes(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
) -> Vec<SmpSaveUnclassifiedTaggedCollectionHeaderProbe> {
|
|
if file_extension_hint != Some("gms") {
|
|
return Vec::new();
|
|
}
|
|
let Some(profile) = container_profile else {
|
|
return Vec::new();
|
|
};
|
|
if !matches!(
|
|
profile.profile_family.as_str(),
|
|
"rt3-classic-save-container-v1"
|
|
| "rt3-105-save-container-v1"
|
|
| "rt3-105-scenario-save-container-v1"
|
|
| "rt3-105-alt-save-container-v1"
|
|
) {
|
|
return Vec::new();
|
|
}
|
|
let known_metadata_tags = BTreeSet::from([
|
|
RT3_SAVE_WORLD_BLOCK_CHUNK_TAG,
|
|
0x000061a9,
|
|
0x00005209,
|
|
0x000036b1,
|
|
EVENT_RUNTIME_COLLECTION_METADATA_TAG as u32,
|
|
]);
|
|
let mut low_tag_offsets: BTreeMap<u32, Vec<usize>> = BTreeMap::new();
|
|
for offset in 0..bytes.len().saturating_sub(4) {
|
|
let Some(tag) = read_u32_at(bytes, offset) else {
|
|
continue;
|
|
};
|
|
if (3..=0xffff).contains(&tag) {
|
|
low_tag_offsets.entry(tag).or_default().push(offset);
|
|
}
|
|
}
|
|
let mut probes = Vec::new();
|
|
for (&metadata_tag, metadata_offsets) in &low_tag_offsets {
|
|
if known_metadata_tags.contains(&metadata_tag) {
|
|
continue;
|
|
}
|
|
let Some(records_offsets) = low_tag_offsets.get(&(metadata_tag + 1)) else {
|
|
continue;
|
|
};
|
|
let Some(close_offsets) = low_tag_offsets.get(&(metadata_tag + 2)) else {
|
|
continue;
|
|
};
|
|
let records_tag = metadata_tag + 1;
|
|
let close_tag = metadata_tag + 2;
|
|
for &metadata_tag_offset in metadata_offsets {
|
|
let mut header_words = [0u32; INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT];
|
|
let mut valid_header = true;
|
|
for (index, word) in header_words.iter_mut().enumerate() {
|
|
let Some(value) = read_u32_at(bytes, metadata_tag_offset + 4 + index * 4) else {
|
|
valid_header = false;
|
|
break;
|
|
};
|
|
*word = value;
|
|
}
|
|
if !valid_header {
|
|
continue;
|
|
}
|
|
let summary = IndexedCollectionHeaderSummary {
|
|
metadata_tag_offset,
|
|
records_tag_offset: 0,
|
|
close_tag_offset: 0,
|
|
direct_collection_flag: header_words[0],
|
|
direct_record_stride: header_words[1],
|
|
live_id_bound: header_words[4],
|
|
live_record_count: header_words[5],
|
|
header_words,
|
|
};
|
|
if !matches!(summary.direct_collection_flag, 0 | 1)
|
|
|| summary.direct_record_stride == 0
|
|
|| summary.direct_record_stride > 0x2000
|
|
|| summary.live_id_bound == 0
|
|
|| summary.live_record_count == 0
|
|
|| summary.live_record_count > summary.live_id_bound
|
|
|| summary.live_id_bound > 0x1000
|
|
|| summary.live_record_count > 0x1000
|
|
{
|
|
continue;
|
|
}
|
|
let records_search_start = metadata_tag_offset + 4;
|
|
let records_index =
|
|
records_offsets.partition_point(|offset| *offset < records_search_start);
|
|
let Some(&records_tag_offset) = records_offsets.get(records_index) else {
|
|
continue;
|
|
};
|
|
let close_search_start = records_tag_offset + 4;
|
|
let close_index = close_offsets.partition_point(|offset| *offset < close_search_start);
|
|
let Some(&close_tag_offset) = close_offsets.get(close_index) else {
|
|
continue;
|
|
};
|
|
let records_span_len = close_tag_offset.saturating_sub(records_tag_offset + 4);
|
|
if records_span_len == 0 || records_span_len < summary.live_record_count as usize {
|
|
continue;
|
|
}
|
|
if probes
|
|
.iter()
|
|
.any(|probe: &SmpSaveUnclassifiedTaggedCollectionHeaderProbe| {
|
|
probe.metadata_tag_offset == metadata_tag_offset
|
|
&& probe.records_tag_offset == records_tag_offset
|
|
&& probe.close_tag_offset == close_tag_offset
|
|
})
|
|
{
|
|
continue;
|
|
}
|
|
probes.push(SmpSaveUnclassifiedTaggedCollectionHeaderProbe {
|
|
profile_family: profile.profile_family.clone(),
|
|
source_kind: "save-unclassified-tagged-header-counts".to_string(),
|
|
semantic_family: "scenario-save-unclassified-tagged-header-counts".to_string(),
|
|
metadata_tag,
|
|
metadata_tag_hex: format!("0x{metadata_tag:08x}"),
|
|
records_tag,
|
|
records_tag_hex: format!("0x{records_tag:08x}"),
|
|
close_tag,
|
|
close_tag_hex: format!("0x{close_tag:08x}"),
|
|
metadata_tag_offset,
|
|
records_tag_offset,
|
|
close_tag_offset,
|
|
records_span_len,
|
|
direct_collection_flag: summary.direct_collection_flag,
|
|
direct_collection_flag_hex: format!("0x{:08x}", summary.direct_collection_flag),
|
|
direct_record_stride: summary.direct_record_stride,
|
|
direct_record_stride_hex: format!("0x{:08x}", summary.direct_record_stride),
|
|
live_id_bound: summary.live_id_bound,
|
|
live_id_bound_hex: format!("0x{:08x}", summary.live_id_bound),
|
|
live_record_count: summary.live_record_count,
|
|
live_record_count_hex: format!("0x{:08x}", summary.live_record_count),
|
|
header_words: summary.header_words.to_vec(),
|
|
header_hex_words: summary
|
|
.header_words
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect(),
|
|
evidence: vec![
|
|
"generic save-side tagged collection scan over plausible low u32 metadata tags not yet claimed by the checked-in collection probes".to_string(),
|
|
"candidate uses adjacent metadata/records/close tags with a header that matches the grounded indexed-collection shape (flag, stride, live_id_bound, live_record_count)".to_string(),
|
|
],
|
|
});
|
|
}
|
|
}
|
|
probes.sort_by(|left, right| {
|
|
right
|
|
.live_record_count
|
|
.cmp(&left.live_record_count)
|
|
.then_with(|| left.metadata_tag.cmp(&right.metadata_tag))
|
|
.then_with(|| left.metadata_tag_offset.cmp(&right.metadata_tag_offset))
|
|
});
|
|
probes.truncate(32);
|
|
probes
|
|
}
|
|
|
|
fn filter_unclassified_tagged_collection_header_probes_outside_known_spans(
|
|
probes: Vec<SmpSaveUnclassifiedTaggedCollectionHeaderProbe>,
|
|
known_header_probes: &[Option<&SmpSaveTaggedCollectionHeaderProbe>],
|
|
) -> Vec<SmpSaveUnclassifiedTaggedCollectionHeaderProbe> {
|
|
probes
|
|
.into_iter()
|
|
.filter(|probe| {
|
|
!known_header_probes.iter().flatten().any(|known| {
|
|
probe.metadata_tag_offset >= known.metadata_tag_offset
|
|
&& probe.close_tag_offset <= known.close_tag_offset
|
|
})
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn parse_save_len_prefixed_ascii_name(bytes: &[u8]) -> Option<String> {
|
|
let len = *bytes.first()? as usize;
|
|
let text_bytes = bytes.get(1..1 + len)?;
|
|
let text = std::str::from_utf8(text_bytes).ok()?.trim_end_matches('\0');
|
|
Some(text.to_string())
|
|
}
|
|
|
|
fn parse_save_varlen_ascii_name_at(bytes: &[u8], offset: usize) -> Option<(String, usize)> {
|
|
let first = *bytes.get(offset)?;
|
|
if first == 0 {
|
|
return None;
|
|
}
|
|
let (len, header_len) = if first <= 0x7f {
|
|
(first as usize, 1usize)
|
|
} else {
|
|
let second = *bytes.get(offset + 1)? as usize;
|
|
((((first as usize) & 0x7f) << 8) | second, 2usize)
|
|
};
|
|
let start = offset + header_len;
|
|
let end = start.checked_add(len)?;
|
|
let text = std::str::from_utf8(bytes.get(start..end)?)
|
|
.ok()?
|
|
.trim_end_matches('\0')
|
|
.to_string();
|
|
Some((text, end))
|
|
}
|
|
|
|
fn parse_save_len_prefixed_ascii_name_pair(bytes: &[u8]) -> Option<(String, String)> {
|
|
let (first, second, _) = parse_save_len_prefixed_ascii_name_triplet(bytes)?;
|
|
Some((first, second))
|
|
}
|
|
|
|
fn parse_save_len_prefixed_ascii_name_triplet_and_consumed_len(
|
|
bytes: &[u8],
|
|
) -> Option<((String, String, Option<String>), usize)> {
|
|
let (first, first_end) = parse_save_varlen_ascii_name_at(bytes, 0)?;
|
|
let mut second_len_offset = first_end;
|
|
while matches!(bytes.get(second_len_offset), Some(0)) {
|
|
second_len_offset += 1;
|
|
}
|
|
let (second, second_end) = parse_save_varlen_ascii_name_at(bytes, second_len_offset)?;
|
|
if first.is_empty() || second.is_empty() {
|
|
return None;
|
|
}
|
|
let mut third_len_offset = second_end;
|
|
while matches!(bytes.get(third_len_offset), Some(0)) {
|
|
third_len_offset += 1;
|
|
}
|
|
let (third, consumed_len) = parse_save_varlen_ascii_name_at(bytes, third_len_offset)
|
|
.map(|(text, end)| {
|
|
let third = (!text.is_empty()).then_some(text);
|
|
(third, end)
|
|
})
|
|
.unwrap_or((None, second_end));
|
|
Some(((first, second, third), consumed_len))
|
|
}
|
|
|
|
fn parse_save_len_prefixed_ascii_name_triplet(
|
|
bytes: &[u8],
|
|
) -> Option<(String, String, Option<String>)> {
|
|
let ((first, second, third), _) =
|
|
parse_save_len_prefixed_ascii_name_triplet_and_consumed_len(bytes)?;
|
|
Some((first, second, third))
|
|
}
|
|
|
|
fn parse_save_fixed_ascii_name(bytes: &[u8]) -> Option<String> {
|
|
let nul_index = bytes
|
|
.iter()
|
|
.position(|byte| *byte == 0)
|
|
.unwrap_or(bytes.len());
|
|
let text = std::str::from_utf8(bytes.get(..nul_index)?).ok()?;
|
|
if text.is_empty()
|
|
|| !text
|
|
.bytes()
|
|
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b' ' | b'-' | b'&' | b'/'))
|
|
{
|
|
return None;
|
|
}
|
|
Some(text.to_string())
|
|
}
|
|
|
|
fn parse_save_region_profile_collection_probe(
|
|
profile_payload: &[u8],
|
|
) -> Option<SmpSaveRegionProfileCollectionProbe> {
|
|
let direct_collection_flag = read_u32_at(profile_payload, 0)?;
|
|
let entry_stride = read_u32_at(profile_payload, 4)?;
|
|
let header_word_2 = read_u32_at(profile_payload, 8)?;
|
|
let header_word_3 = read_u32_at(profile_payload, 12)?;
|
|
let live_id_bound = read_u32_at(profile_payload, 16)?;
|
|
let live_record_count = read_u32_at(profile_payload, 20)?;
|
|
let header_word_6 = read_u32_at(profile_payload, 24)?;
|
|
let header_word_7 = read_u32_at(profile_payload, 28)?;
|
|
if !(direct_collection_flag == 1
|
|
&& entry_stride == 0x22
|
|
&& header_word_2 == 2
|
|
&& header_word_3 == 2
|
|
&& live_record_count > 0
|
|
&& live_record_count < live_id_bound
|
|
&& header_word_6 == 0
|
|
&& header_word_7 == 1)
|
|
{
|
|
return None;
|
|
}
|
|
let entry_stride = entry_stride as usize;
|
|
let live_record_count_usize = live_record_count as usize;
|
|
let rows_byte_len = live_record_count_usize.checked_mul(entry_stride)?;
|
|
let mut matched_probe = None;
|
|
for entry_start_relative_offset in 0x20..=0x80 {
|
|
if entry_start_relative_offset + rows_byte_len > profile_payload.len() {
|
|
break;
|
|
}
|
|
let mut entries = Vec::with_capacity(live_record_count_usize);
|
|
let mut matched = true;
|
|
for entry_index in 0..live_record_count_usize {
|
|
let row_relative_offset = entry_start_relative_offset + entry_index * entry_stride;
|
|
let row =
|
|
profile_payload.get(row_relative_offset..row_relative_offset + entry_stride)?;
|
|
let name = match parse_save_fixed_ascii_name(row.get(..12)?) {
|
|
Some(name) => name,
|
|
None => {
|
|
matched = false;
|
|
break;
|
|
}
|
|
};
|
|
let trailing_weight_f32 = f32::from_bits(read_u32_at(row, entry_stride - 4)?);
|
|
if !trailing_weight_f32.is_finite() || trailing_weight_f32 < 0.0 {
|
|
matched = false;
|
|
break;
|
|
}
|
|
entries.push(SmpSaveRegionProfileEntryProbe {
|
|
entry_index,
|
|
row_relative_offset,
|
|
name,
|
|
trailing_weight_f32,
|
|
});
|
|
}
|
|
if matched {
|
|
matched_probe = Some(SmpSaveRegionProfileCollectionProbe {
|
|
direct_collection_flag,
|
|
entry_stride: entry_stride as u32,
|
|
live_id_bound,
|
|
live_record_count,
|
|
entry_start_relative_offset,
|
|
trailing_padding_len: profile_payload.len()
|
|
- (entry_start_relative_offset + rows_byte_len),
|
|
entries,
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
matched_probe
|
|
}
|
|
|
|
fn parse_rt3_105_save_name_table_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
bridge_payload_probe: Option<&SmpRt3105SaveBridgePayloadProbe>,
|
|
) -> Option<SmpRt3105SaveNameTableProbe> {
|
|
let (
|
|
profile_family,
|
|
source_kind,
|
|
header_offset,
|
|
entries_offset,
|
|
block_end_offset,
|
|
mut evidence,
|
|
) = if let Some(payload) = bridge_payload_probe {
|
|
(
|
|
payload.profile_family.clone(),
|
|
"save-bridge-secondary-block".to_string(),
|
|
payload.secondary_block_offset + 0x354,
|
|
payload.secondary_block_offset + 0x3b5,
|
|
payload.secondary_block_end_offset,
|
|
vec![
|
|
"common-save bridge payload branch".to_string(),
|
|
format!(
|
|
"secondary block span 0x{:x}..0x{:x}",
|
|
payload.secondary_block_offset, payload.secondary_block_end_offset
|
|
),
|
|
],
|
|
)
|
|
} else {
|
|
let profile_family = container_profile
|
|
.map(|profile| profile.profile_family.clone())
|
|
.unwrap_or_else(|| "unknown".to_string());
|
|
let extension = file_extension_hint.unwrap_or("");
|
|
let source_kind = match extension {
|
|
"gmp" => "map-fixed-catalog-range",
|
|
"gms" => "save-fixed-catalog-range",
|
|
"gmx" => "sandbox-fixed-catalog-range",
|
|
_ => "fixed-catalog-range",
|
|
}
|
|
.to_string();
|
|
(
|
|
profile_family,
|
|
source_kind,
|
|
0x6a70,
|
|
0x6ad1,
|
|
0x73c0,
|
|
vec![
|
|
"fixed catalog range branch".to_string(),
|
|
"using observed shared 1.05 candidate-availability table offsets".to_string(),
|
|
],
|
|
)
|
|
};
|
|
let entry_stride = 0x22usize;
|
|
if block_end_offset > bytes.len() {
|
|
return None;
|
|
}
|
|
if !matches_candidate_availability_table_header(bytes, header_offset) {
|
|
return None;
|
|
}
|
|
let observed_entry_capacity = read_u32_at(bytes, header_offset + 0x1c)? as usize;
|
|
let observed_entry_count = read_u32_at(bytes, header_offset + 0x20)? as usize;
|
|
let entries_len = observed_entry_count.checked_mul(entry_stride)?;
|
|
let entries_end_offset = entries_offset.checked_add(entries_len)?;
|
|
if observed_entry_count == 0 || observed_entry_capacity < observed_entry_count {
|
|
return None;
|
|
}
|
|
if entries_end_offset > block_end_offset || entries_end_offset > bytes.len() {
|
|
return None;
|
|
}
|
|
|
|
let mut entries = Vec::with_capacity(observed_entry_count);
|
|
for index in 0..observed_entry_count {
|
|
let offset = entries_offset + index * entry_stride;
|
|
let chunk = &bytes[offset..offset + entry_stride];
|
|
let nul_index = chunk
|
|
.iter()
|
|
.position(|byte| *byte == 0)
|
|
.unwrap_or(entry_stride);
|
|
let text = std::str::from_utf8(&chunk[..nul_index]).ok()?.to_string();
|
|
let trailer_word = read_u32_at(bytes, offset + entry_stride - 4)?;
|
|
entries.push(SmpRt3105SaveNameTableEntry {
|
|
index,
|
|
offset,
|
|
text,
|
|
availability_dword: trailer_word,
|
|
availability_dword_hex: format!("0x{trailer_word:08x}"),
|
|
trailer_word,
|
|
trailer_word_hex: format!("0x{trailer_word:08x}"),
|
|
});
|
|
}
|
|
|
|
let zero_trailer_entry_names = entries
|
|
.iter()
|
|
.filter(|entry| entry.trailer_word == 0)
|
|
.map(|entry| entry.text.clone())
|
|
.collect::<Vec<_>>();
|
|
let zero_trailer_entry_count = zero_trailer_entry_names.len();
|
|
let nonzero_trailer_entry_count = entries.len().saturating_sub(zero_trailer_entry_count);
|
|
let mut distinct_trailer_words = entries
|
|
.iter()
|
|
.map(|entry| entry.trailer_word)
|
|
.collect::<Vec<_>>();
|
|
distinct_trailer_words.sort_unstable();
|
|
distinct_trailer_words.dedup();
|
|
let distinct_trailer_hex_words = distinct_trailer_words
|
|
.iter()
|
|
.map(|word| format!("0x{word:08x}"))
|
|
.collect::<Vec<_>>();
|
|
let trailing_footer_hex = hex_encode(&bytes[entries_end_offset..block_end_offset]);
|
|
let footer = &bytes[entries_end_offset..block_end_offset];
|
|
if footer.len() != 9 {
|
|
return None;
|
|
}
|
|
let footer_progress_word_0 = u32::from_le_bytes([footer[0], footer[1], footer[2], footer[3]]);
|
|
let footer_progress_word_1 = u32::from_le_bytes([footer[4], footer[5], footer[6], footer[7]]);
|
|
let footer_trailing_byte = footer[8];
|
|
let mut footer_grounded_alignments = Vec::new();
|
|
for value in [footer_progress_word_0, footer_progress_word_1] {
|
|
if let Some(alignment) = classify_name_table_footer_progress_alignment(value) {
|
|
footer_grounded_alignments.push(alignment.to_string());
|
|
}
|
|
}
|
|
evidence.extend([
|
|
format!("header offset 0x{header_offset:08x}"),
|
|
format!("entries offset 0x{entries_offset:08x}"),
|
|
format!("entry stride 0x{entry_stride:x}"),
|
|
format!("observed entry capacity {}", observed_entry_capacity),
|
|
format!("observed entry count {}", observed_entry_count),
|
|
format!("zero-trailer entries {}", zero_trailer_entry_count),
|
|
format!(
|
|
"trailing footer {} bytes after last entry",
|
|
block_end_offset - entries_end_offset
|
|
),
|
|
]);
|
|
let semantic_alignment = vec![
|
|
"Matches the grounded scenario-side named candidate-availability table shape under 0x00437743.".to_string(),
|
|
"Entry layout matches 0x00434ea0/0x00434f20: name slot at +0x00..+0x1d and availability dword at +0x1e.".to_string(),
|
|
"The shared map/save range suggests this catalog is bundled in source map content and later mirrored into scenario state [state+0x66b2].".to_string(),
|
|
];
|
|
|
|
Some(SmpRt3105SaveNameTableProbe {
|
|
profile_family,
|
|
source_kind,
|
|
semantic_family: "scenario-named-candidate-availability-table".to_string(),
|
|
semantic_alignment,
|
|
header_offset,
|
|
header_word_0: read_u32_at(bytes, header_offset)?,
|
|
header_word_0_hex: format!("0x{:08x}", read_u32_at(bytes, header_offset)?),
|
|
header_word_1: read_u32_at(bytes, header_offset + 4)?,
|
|
header_word_1_hex: format!("0x{:08x}", read_u32_at(bytes, header_offset + 4)?),
|
|
header_word_2: read_u32_at(bytes, header_offset + 8)?,
|
|
header_word_2_hex: format!("0x{:08x}", read_u32_at(bytes, header_offset + 8)?),
|
|
entry_stride,
|
|
entry_stride_hex: format!("0x{entry_stride:x}"),
|
|
header_prefix_word_count: 11,
|
|
observed_entry_capacity,
|
|
observed_entry_count,
|
|
zero_trailer_entry_count,
|
|
nonzero_trailer_entry_count,
|
|
distinct_trailer_words,
|
|
distinct_trailer_hex_words,
|
|
zero_trailer_entry_names,
|
|
entries_offset,
|
|
entries_end_offset,
|
|
trailing_footer_hex,
|
|
footer_progress_word_0,
|
|
footer_progress_word_0_hex: format!("0x{footer_progress_word_0:08x}"),
|
|
footer_progress_word_1,
|
|
footer_progress_word_1_hex: format!("0x{footer_progress_word_1:08x}"),
|
|
footer_trailing_byte,
|
|
footer_trailing_byte_hex: format!("0x{footer_trailing_byte:02x}"),
|
|
footer_grounded_alignments,
|
|
entries,
|
|
evidence,
|
|
})
|
|
}
|
|
|
|
const RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE: usize = 0x41;
|
|
const RT3_105_SAVE_NAMED_LOCOMOTIVE_MIN_ENTRY_COUNT: usize = 8;
|
|
const RT3_105_SAVE_NAMED_LOCOMOTIVE_MAX_SEARCH_SPAN: usize = 0x4000;
|
|
|
|
fn parse_rt3_105_save_named_locomotive_availability_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
packed_profile_probe: Option<&SmpRt3105PackedProfileProbe>,
|
|
) -> Option<SmpRt3105SaveNamedLocomotiveAvailabilityProbe> {
|
|
let packed_profile_probe = packed_profile_probe?;
|
|
let extension = file_extension_hint.unwrap_or("");
|
|
let profile_family = container_profile
|
|
.map(|profile| profile.profile_family.clone())
|
|
.unwrap_or_else(|| packed_profile_probe.profile_family.clone());
|
|
if !matches!(extension, "gms" | "gmx") || !profile_family.contains("save-container") {
|
|
return None;
|
|
}
|
|
|
|
let search_start = packed_profile_probe
|
|
.packed_profile_offset
|
|
.checked_add(packed_profile_probe.packed_profile_len)?;
|
|
let search_end = search_start
|
|
.checked_add(RT3_105_SAVE_NAMED_LOCOMOTIVE_MAX_SEARCH_SPAN)
|
|
.map(|end| end.min(bytes.len()))
|
|
.unwrap_or(bytes.len());
|
|
if search_end <= search_start + RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE {
|
|
return None;
|
|
}
|
|
|
|
let mut best_start = None;
|
|
let mut best_entries = Vec::new();
|
|
for candidate_start in search_start..search_end {
|
|
let entries = parse_direct_named_locomotive_entries(bytes, candidate_start, search_end);
|
|
if entries.len() > best_entries.len() {
|
|
best_entries = entries;
|
|
best_start = Some(candidate_start);
|
|
}
|
|
}
|
|
|
|
if best_entries.len() < RT3_105_SAVE_NAMED_LOCOMOTIVE_MIN_ENTRY_COUNT {
|
|
return None;
|
|
}
|
|
|
|
let entries_offset = best_start?;
|
|
let entries_end_offset = entries_offset
|
|
.checked_add(best_entries.len() * RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE)?;
|
|
let zero_availability_names = best_entries
|
|
.iter()
|
|
.filter(|entry| entry.availability_dword == 0)
|
|
.map(|entry| entry.text.clone())
|
|
.collect::<Vec<_>>();
|
|
let zero_availability_count = zero_availability_names.len();
|
|
let source_kind = match extension {
|
|
"gms" => "save-direct-locomotive-row-run",
|
|
"gmx" => "sandbox-direct-locomotive-row-run",
|
|
_ => "direct-locomotive-row-run",
|
|
}
|
|
.to_string();
|
|
|
|
let observed_entry_count = best_entries.len();
|
|
|
|
Some(SmpRt3105SaveNamedLocomotiveAvailabilityProbe {
|
|
profile_family,
|
|
source_kind,
|
|
semantic_family: "scenario-named-locomotive-availability-table".to_string(),
|
|
semantic_alignment: vec![
|
|
"Matches the grounded `.smp` save-side locomotive-name-plus-dword row family restored into scenario state [world+0x66b6].".to_string(),
|
|
"Entry layout is one availability dword at +0x00 followed by one fixed-width locomotive name buffer at +0x04..+0x40.".to_string(),
|
|
"The recovered row order is treated conservatively as the live locomotive ordinal order later used by locomotives-page descriptor lowering.".to_string(),
|
|
],
|
|
entries_offset,
|
|
entry_stride: RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE,
|
|
entry_stride_hex: format!("0x{:x}", RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE),
|
|
observed_entry_count,
|
|
zero_availability_count,
|
|
zero_availability_names,
|
|
entries_end_offset,
|
|
entries: best_entries,
|
|
evidence: vec![
|
|
format!("search span 0x{search_start:08x}..0x{search_end:08x}"),
|
|
format!("entries offset 0x{entries_offset:08x}"),
|
|
format!(
|
|
"entry stride 0x{:x}",
|
|
RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE
|
|
),
|
|
format!("observed entry count {observed_entry_count}"),
|
|
],
|
|
})
|
|
}
|
|
|
|
fn parse_direct_named_locomotive_entries(
|
|
bytes: &[u8],
|
|
start_offset: usize,
|
|
search_end: usize,
|
|
) -> Vec<SmpRt3105SaveNameTableEntry> {
|
|
let mut entries = Vec::new();
|
|
let mut offset = start_offset;
|
|
while offset + RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE <= bytes.len() && offset < search_end
|
|
{
|
|
let record = &bytes[offset..offset + RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE];
|
|
let Some(nul_index) = record[4..].iter().position(|byte| *byte == 0) else {
|
|
break;
|
|
};
|
|
let name_bytes = &record[4..4 + nul_index];
|
|
if name_bytes.is_empty() {
|
|
break;
|
|
}
|
|
let Ok(text) = std::str::from_utf8(name_bytes) else {
|
|
break;
|
|
};
|
|
if !is_probable_named_locomotive_label(text) {
|
|
break;
|
|
}
|
|
if record[4 + nul_index + 1..].iter().any(|byte| *byte != 0) {
|
|
break;
|
|
}
|
|
|
|
let availability_dword = u32::from_le_bytes([record[0], record[1], record[2], record[3]]);
|
|
entries.push(SmpRt3105SaveNameTableEntry {
|
|
index: entries.len(),
|
|
offset,
|
|
text: text.to_string(),
|
|
availability_dword,
|
|
availability_dword_hex: format!("0x{availability_dword:08x}"),
|
|
trailer_word: availability_dword,
|
|
trailer_word_hex: format!("0x{availability_dword:08x}"),
|
|
});
|
|
offset += RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE;
|
|
}
|
|
entries
|
|
}
|
|
|
|
fn is_probable_named_locomotive_label(text: &str) -> bool {
|
|
if text.is_empty() || text.len() > 40 {
|
|
return false;
|
|
}
|
|
text.bytes().all(|byte| {
|
|
byte.is_ascii_alphanumeric() || matches!(byte, b' ' | b'-' | b'/' | b'(' | b')' | b'.')
|
|
})
|
|
}
|
|
|
|
fn parse_special_conditions_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
) -> Option<SmpSpecialConditionsProbe> {
|
|
let table_len = SPECIAL_CONDITION_COUNT.checked_mul(4)?;
|
|
let table_end = SPECIAL_CONDITIONS_OFFSET.checked_add(table_len)?;
|
|
if table_end > bytes.len() {
|
|
return None;
|
|
}
|
|
|
|
let mut entries = Vec::with_capacity(SPECIAL_CONDITION_COUNT);
|
|
for definition in KNOWN_SPECIAL_CONDITION_DEFINITIONS {
|
|
let value = read_u32_at(
|
|
bytes,
|
|
SPECIAL_CONDITIONS_OFFSET + (definition.slot_index as usize) * 4,
|
|
)?;
|
|
if value > 1 {
|
|
return None;
|
|
}
|
|
entries.push(SmpSpecialConditionEntry {
|
|
slot_index: definition.slot_index,
|
|
hidden: definition.hidden,
|
|
label_id: definition.label_id,
|
|
help_id: definition.help_id,
|
|
label: definition.label.to_string(),
|
|
value,
|
|
value_hex: format!("0x{value:08x}"),
|
|
});
|
|
}
|
|
|
|
let hidden_sentinel = entries
|
|
.iter()
|
|
.find(|entry| entry.slot_index == SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT as u8)?;
|
|
if hidden_sentinel.value != 1 {
|
|
return None;
|
|
}
|
|
|
|
let enabled_visible_labels = entries
|
|
.iter()
|
|
.filter(|entry| !entry.hidden && entry.value != 0)
|
|
.map(|entry| entry.label.clone())
|
|
.collect::<Vec<_>>();
|
|
let source_kind = match file_extension_hint.unwrap_or("") {
|
|
"gmp" => "map-fixed-special-conditions-range",
|
|
"gms" => "save-fixed-special-conditions-range",
|
|
"gmx" => "sandbox-fixed-special-conditions-range",
|
|
_ => "fixed-special-conditions-range",
|
|
}
|
|
.to_string();
|
|
let profile_family = container_profile
|
|
.map(|profile| profile.profile_family.clone())
|
|
.unwrap_or_else(|| "unknown".to_string());
|
|
let mut evidence = vec![
|
|
format!("fixed 36-dword range at 0x{SPECIAL_CONDITIONS_OFFSET:04x}"),
|
|
"all observed lanes are boolean dwords".to_string(),
|
|
"hidden slot 35 carries the expected sentinel value 1".to_string(),
|
|
"slot metadata matches the grounded editor special-conditions table at 0x005f3ab0"
|
|
.to_string(),
|
|
];
|
|
if enabled_visible_labels.is_empty() {
|
|
evidence.push("no visible special conditions enabled in this file".to_string());
|
|
} else {
|
|
evidence.push(format!(
|
|
"enabled visible conditions: {}",
|
|
enabled_visible_labels.join(", ")
|
|
));
|
|
}
|
|
|
|
Some(SmpSpecialConditionsProbe {
|
|
profile_family,
|
|
source_kind,
|
|
table_offset: SPECIAL_CONDITIONS_OFFSET,
|
|
table_len,
|
|
enabled_visible_count: enabled_visible_labels.len(),
|
|
enabled_visible_labels,
|
|
hidden_sentinel_slot_index: SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT as u8,
|
|
hidden_sentinel_value: hidden_sentinel.value,
|
|
hidden_sentinel_value_hex: hidden_sentinel.value_hex.clone(),
|
|
entries,
|
|
evidence,
|
|
})
|
|
}
|
|
|
|
fn parse_post_special_conditions_scalar_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
special_conditions_probe: Option<&SmpSpecialConditionsProbe>,
|
|
) -> Option<SmpPostSpecialConditionsScalarProbe> {
|
|
special_conditions_probe?;
|
|
if POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET > bytes.len() {
|
|
return None;
|
|
}
|
|
|
|
let dword_count =
|
|
(POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET - POST_SPECIAL_CONDITIONS_SCALAR_OFFSET) / 4;
|
|
let mut nonzero_lanes = Vec::new();
|
|
for index in 0..dword_count {
|
|
let absolute_offset = POST_SPECIAL_CONDITIONS_SCALAR_OFFSET + index * 4;
|
|
let value = read_u32_at(bytes, absolute_offset)?;
|
|
if value == 0 {
|
|
continue;
|
|
}
|
|
nonzero_lanes.push(SmpPostSpecialConditionsScalarLane {
|
|
absolute_offset,
|
|
relative_offset: absolute_offset - POST_SPECIAL_CONDITIONS_SCALAR_OFFSET,
|
|
absolute_offset_hex: format!("0x{absolute_offset:04x}"),
|
|
relative_offset_hex: format!(
|
|
"0x{:x}",
|
|
absolute_offset - POST_SPECIAL_CONDITIONS_SCALAR_OFFSET
|
|
),
|
|
value,
|
|
value_hex: format!("0x{value:08x}"),
|
|
probable_f32_le: probable_normal_f32_string(value),
|
|
});
|
|
}
|
|
|
|
let source_kind = match file_extension_hint.unwrap_or("") {
|
|
"gmp" => "map-post-special-conditions-window",
|
|
"gms" => "save-post-special-conditions-window",
|
|
"gmx" => "sandbox-post-special-conditions-window",
|
|
_ => "post-special-conditions-window",
|
|
}
|
|
.to_string();
|
|
let profile_family = container_profile
|
|
.map(|profile| profile.profile_family.clone())
|
|
.unwrap_or_else(|| "unknown".to_string());
|
|
let first_nonzero_offset = nonzero_lanes.first().map(|lane| lane.absolute_offset);
|
|
let last_nonzero_offset = nonzero_lanes.last().map(|lane| lane.absolute_offset);
|
|
let overlap_nonzero_relative_offset_hexes = nonzero_lanes
|
|
.iter()
|
|
.filter(|lane| lane.absolute_offset < POST_SPECIAL_CONDITIONS_SCALAR_OVERLAP_END_OFFSET)
|
|
.map(|lane| lane.relative_offset_hex.clone())
|
|
.collect::<Vec<_>>();
|
|
let tail_nonzero_lanes = nonzero_lanes
|
|
.iter()
|
|
.filter(|lane| lane.absolute_offset >= POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET)
|
|
.cloned()
|
|
.collect::<Vec<_>>();
|
|
let tail_first_nonzero_offset = tail_nonzero_lanes.first().map(|lane| lane.absolute_offset);
|
|
let tail_last_nonzero_offset = tail_nonzero_lanes.last().map(|lane| lane.absolute_offset);
|
|
let tail_nonzero_relative_offset_hexes = tail_nonzero_lanes
|
|
.iter()
|
|
.map(|lane| lane.relative_offset_hex.clone())
|
|
.collect::<Vec<_>>();
|
|
let grounded_text_field_remaining_file_window = &bytes[POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET
|
|
..POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET];
|
|
let mut grounded_text_field_remaining_nonzero_offsets = Vec::new();
|
|
for (index, byte) in grounded_text_field_remaining_file_window.iter().enumerate() {
|
|
if *byte != 0 {
|
|
grounded_text_field_remaining_nonzero_offsets
|
|
.push(POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET + index);
|
|
}
|
|
}
|
|
let grounded_text_field_remaining_first_nonzero_offset =
|
|
grounded_text_field_remaining_nonzero_offsets
|
|
.first()
|
|
.copied();
|
|
let grounded_text_field_remaining_last_nonzero_offset =
|
|
grounded_text_field_remaining_nonzero_offsets
|
|
.last()
|
|
.copied();
|
|
let mut evidence = vec![
|
|
format!(
|
|
"fixed post-sentinel dword window at 0x{POST_SPECIAL_CONDITIONS_SCALAR_OFFSET:04x}..0x{POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET:04x}"
|
|
),
|
|
"window starts immediately after the hidden special-conditions sentinel slot at 0x0df0"
|
|
.to_string(),
|
|
format!(
|
|
"leading overlap prefix 0x{POST_SPECIAL_CONDITIONS_SCALAR_OFFSET:04x}..0x{POST_SPECIAL_CONDITIONS_SCALAR_OVERLAP_END_OFFSET:04x} aliases aligned runtime-rule band indices {}..{}",
|
|
SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX,
|
|
SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX
|
|
+ SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT
|
|
- 1
|
|
),
|
|
format!("save-only tail begins at 0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET:04x}"),
|
|
format!(
|
|
"that tail is offset-aligned with live runtime object bytes [world+0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_OFFSET:04x}..+0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_END_OFFSET:04x}]"
|
|
),
|
|
format!(
|
|
"the tail start lands on the grounded live field [world+0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_OFFSET:04x}], a 0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_LEN:x}-byte status-text buffer written by win/lose and winner-announcement helpers"
|
|
),
|
|
format!(
|
|
"current dword scan stops at 0x{POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET:04x}, leaving one byte-aligned continuation window 0x{POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET:04x}..0x{POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET:04x} before the next clean live-field edge at [world+0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_END_OFFSET:04x}]"
|
|
),
|
|
format!(
|
|
"the next exact grounded fields after that edge begin at [world+0x{POST_TEXT_FIELD_0_RUNTIME_OBJECT_OFFSET:04x}], [world+0x{POST_TEXT_FIELD_1_RUNTIME_OBJECT_OFFSET:04x}], [world+0x{POST_TEXT_FIELD_2_RUNTIME_OBJECT_OFFSET:04x}], [world+0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_OFFSET:04x}], and [world+0x{POST_TEXT_FIELD_4_RUNTIME_OBJECT_OFFSET:04x}], which map to file offsets 0x{POST_TEXT_FIELD_NEIGHBORHOOD_OFFSET:04x}, 0x0f5d, 0x0f61, 0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_FILE_OFFSET:04x}, and 0x0f6d"
|
|
),
|
|
format!(
|
|
"the first grounded dword-sized fields after that edge are [world+0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_OFFSET:04x}] and [world+0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_OFFSET:04x}], which would land at file offsets 0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_FILE_OFFSET:04x} and 0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_FILE_OFFSET:04x}"
|
|
),
|
|
];
|
|
if nonzero_lanes.is_empty() {
|
|
evidence.push(
|
|
"all observed dwords in this post-sentinel window are zero for this file".to_string(),
|
|
);
|
|
} else {
|
|
evidence.push(format!(
|
|
"observed {} nonzero dword lanes between {} and {}",
|
|
nonzero_lanes.len(),
|
|
nonzero_lanes
|
|
.first()
|
|
.map(|lane| lane.absolute_offset_hex.as_str())
|
|
.unwrap_or("n/a"),
|
|
nonzero_lanes
|
|
.last()
|
|
.map(|lane| lane.absolute_offset_hex.as_str())
|
|
.unwrap_or("n/a")
|
|
));
|
|
if nonzero_lanes
|
|
.iter()
|
|
.all(|lane| lane.probable_f32_le.is_some())
|
|
{
|
|
evidence.push(
|
|
"every nonzero lane in this window also decodes as a normal finite little-endian f32"
|
|
.to_string(),
|
|
);
|
|
}
|
|
evidence.push(format!(
|
|
"{} nonzero lanes fall inside the aligned-band overlap prefix and {} fall inside the later tail",
|
|
overlap_nonzero_relative_offset_hexes.len(),
|
|
tail_nonzero_lanes.len()
|
|
));
|
|
}
|
|
evidence.push(
|
|
"checked file bytes in the later tail are not yet validated as a byte-for-byte mirror of the live object, because the region aligned to [world+0x4b47] does not currently decode as preserved text in the checked saves"
|
|
.to_string(),
|
|
);
|
|
if grounded_text_field_remaining_nonzero_offsets.is_empty() {
|
|
evidence.push(
|
|
"the remaining file window through the grounded text-field edge is all zero in this file"
|
|
.to_string(),
|
|
);
|
|
} else {
|
|
evidence.push(format!(
|
|
"the remaining file window through the grounded text-field edge still has {} nonzero bytes between 0x{:04x} and 0x{:04x}",
|
|
grounded_text_field_remaining_nonzero_offsets.len(),
|
|
grounded_text_field_remaining_first_nonzero_offset.unwrap_or(0),
|
|
grounded_text_field_remaining_last_nonzero_offset.unwrap_or(0)
|
|
));
|
|
}
|
|
|
|
Some(SmpPostSpecialConditionsScalarProbe {
|
|
profile_family,
|
|
source_kind,
|
|
window_offset: POST_SPECIAL_CONDITIONS_SCALAR_OFFSET,
|
|
window_end_offset: POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET,
|
|
window_len: POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET
|
|
- POST_SPECIAL_CONDITIONS_SCALAR_OFFSET,
|
|
window_len_hex: format!(
|
|
"0x{:x}",
|
|
POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET - POST_SPECIAL_CONDITIONS_SCALAR_OFFSET
|
|
),
|
|
dword_count,
|
|
overlap_end_offset: POST_SPECIAL_CONDITIONS_SCALAR_OVERLAP_END_OFFSET,
|
|
overlap_end_offset_hex: format!(
|
|
"0x{POST_SPECIAL_CONDITIONS_SCALAR_OVERLAP_END_OFFSET:04x}"
|
|
),
|
|
overlap_dword_count: (POST_SPECIAL_CONDITIONS_SCALAR_OVERLAP_END_OFFSET
|
|
- POST_SPECIAL_CONDITIONS_SCALAR_OFFSET)
|
|
/ 4,
|
|
overlap_nonzero_dword_count: overlap_nonzero_relative_offset_hexes.len(),
|
|
overlap_nonzero_relative_offset_hexes,
|
|
tail_offset: POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET,
|
|
tail_offset_hex: format!("0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET:04x}"),
|
|
tail_len: POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET
|
|
- POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET,
|
|
tail_len_hex: format!(
|
|
"0x{:x}",
|
|
POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET - POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET
|
|
),
|
|
tail_dword_count: (POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET
|
|
- POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET)
|
|
/ 4,
|
|
tail_runtime_object_offset: POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_OFFSET,
|
|
tail_runtime_object_offset_hex: format!(
|
|
"0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_OFFSET:04x}"
|
|
),
|
|
tail_runtime_object_end_offset:
|
|
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_END_OFFSET,
|
|
tail_runtime_object_end_offset_hex: format!(
|
|
"0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_END_OFFSET:04x}"
|
|
),
|
|
tail_runtime_object_validated_byte_mirror: false,
|
|
tail_grounded_live_field_offset:
|
|
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_OFFSET,
|
|
tail_grounded_live_field_offset_hex: format!(
|
|
"0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_OFFSET:04x}"
|
|
),
|
|
tail_grounded_live_field_name: "victory-or-outcome status text buffer".to_string(),
|
|
tail_grounded_live_field_copy_len:
|
|
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_LEN,
|
|
tail_grounded_live_field_copy_len_hex: format!(
|
|
"0x{:x}",
|
|
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_LEN
|
|
),
|
|
tail_grounded_live_field_copy_end_offset:
|
|
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_END_OFFSET,
|
|
tail_grounded_live_field_copy_end_offset_hex: format!(
|
|
"0x{POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_END_OFFSET:04x}"
|
|
),
|
|
tail_window_cuts_through_grounded_live_field:
|
|
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_END_OFFSET
|
|
< POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_END_OFFSET,
|
|
tail_grounded_live_field_remaining_file_window_offset:
|
|
POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET,
|
|
tail_grounded_live_field_remaining_file_window_offset_hex: format!(
|
|
"0x{POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET:04x}"
|
|
),
|
|
tail_grounded_live_field_remaining_file_window_len:
|
|
POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET
|
|
- POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET,
|
|
tail_grounded_live_field_remaining_file_window_len_hex: format!(
|
|
"0x{:x}",
|
|
POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET
|
|
- POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET
|
|
),
|
|
tail_grounded_live_field_remaining_file_window_nonzero_byte_count:
|
|
grounded_text_field_remaining_nonzero_offsets.len(),
|
|
tail_grounded_live_field_remaining_file_window_first_nonzero_offset:
|
|
grounded_text_field_remaining_first_nonzero_offset,
|
|
tail_grounded_live_field_remaining_file_window_first_nonzero_offset_hex:
|
|
grounded_text_field_remaining_first_nonzero_offset
|
|
.map(|offset| format!("0x{offset:04x}")),
|
|
tail_grounded_live_field_remaining_file_window_last_nonzero_offset:
|
|
grounded_text_field_remaining_last_nonzero_offset,
|
|
tail_grounded_live_field_remaining_file_window_last_nonzero_offset_hex:
|
|
grounded_text_field_remaining_last_nonzero_offset
|
|
.map(|offset| format!("0x{offset:04x}")),
|
|
tail_next_grounded_dword_field_offset:
|
|
POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_OFFSET,
|
|
tail_next_grounded_dword_field_offset_hex: format!(
|
|
"0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_OFFSET:04x}"
|
|
),
|
|
tail_next_grounded_dword_field_file_offset:
|
|
POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_FILE_OFFSET,
|
|
tail_next_grounded_dword_field_file_offset_hex: format!(
|
|
"0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_0_FILE_OFFSET:04x}"
|
|
),
|
|
tail_second_grounded_dword_field_offset:
|
|
POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_OFFSET,
|
|
tail_second_grounded_dword_field_offset_hex: format!(
|
|
"0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_OFFSET:04x}"
|
|
),
|
|
tail_second_grounded_dword_field_file_offset:
|
|
POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_FILE_OFFSET,
|
|
tail_second_grounded_dword_field_file_offset_hex: format!(
|
|
"0x{POST_SPECIAL_CONDITIONS_NEXT_GROUNDED_DWORD_FIELD_1_FILE_OFFSET:04x}"
|
|
),
|
|
post_text_field_file_alignment_matches_grounded_dword_fields: false,
|
|
tail_nonzero_dword_count: tail_nonzero_lanes.len(),
|
|
tail_first_nonzero_offset,
|
|
tail_first_nonzero_offset_hex: tail_first_nonzero_offset
|
|
.map(|offset| format!("0x{offset:04x}")),
|
|
tail_last_nonzero_offset,
|
|
tail_last_nonzero_offset_hex: tail_last_nonzero_offset
|
|
.map(|offset| format!("0x{offset:04x}")),
|
|
tail_nonzero_relative_offset_hexes,
|
|
nonzero_dword_count: nonzero_lanes.len(),
|
|
first_nonzero_offset,
|
|
first_nonzero_offset_hex: first_nonzero_offset.map(|offset| format!("0x{offset:04x}")),
|
|
last_nonzero_offset,
|
|
last_nonzero_offset_hex: last_nonzero_offset.map(|offset| format!("0x{offset:04x}")),
|
|
nonzero_lanes,
|
|
evidence,
|
|
})
|
|
}
|
|
|
|
fn parse_post_text_field_neighborhood_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
special_conditions_probe: Option<&SmpSpecialConditionsProbe>,
|
|
) -> Option<SmpPostTextFieldNeighborhoodProbe> {
|
|
special_conditions_probe?;
|
|
if POST_TEXT_FIELD_NEIGHBORHOOD_END_OFFSET > bytes.len() {
|
|
return None;
|
|
}
|
|
|
|
let profile_family = container_profile
|
|
.map(|profile| profile.profile_family.clone())
|
|
.unwrap_or_else(|| "unknown".to_string());
|
|
let source_kind = match file_extension_hint.unwrap_or("") {
|
|
"gmp" => "post-text-grounded-field-neighborhood",
|
|
"gms" => "post-text-grounded-field-neighborhood",
|
|
"gmx" => "post-text-grounded-field-neighborhood",
|
|
_ => "post-text-grounded-field-neighborhood",
|
|
}
|
|
.to_string();
|
|
|
|
let exact_fields = [
|
|
(
|
|
"Auto-Show Grade During Track Lay",
|
|
POST_TEXT_FIELD_0_RUNTIME_OBJECT_OFFSET,
|
|
POST_TEXT_FIELD_NEIGHBORHOOD_OFFSET,
|
|
1usize,
|
|
),
|
|
(
|
|
"Starting Building Density Level",
|
|
POST_TEXT_FIELD_1_RUNTIME_OBJECT_OFFSET,
|
|
0x0f5dusize,
|
|
1usize,
|
|
),
|
|
(
|
|
"Building Density Growth",
|
|
POST_TEXT_FIELD_2_RUNTIME_OBJECT_OFFSET,
|
|
0x0f61usize,
|
|
1usize,
|
|
),
|
|
(
|
|
"leftover simulation time accumulator",
|
|
POST_TEXT_FIELD_3_RUNTIME_OBJECT_OFFSET,
|
|
0x0f65usize,
|
|
4usize,
|
|
),
|
|
(
|
|
"selected-year lane snapshot",
|
|
POST_TEXT_FIELD_4_RUNTIME_OBJECT_OFFSET,
|
|
0x0f6dusize,
|
|
1usize,
|
|
),
|
|
(
|
|
"late locomotive policy gate dword",
|
|
POST_TEXT_FIELD_5_RUNTIME_OBJECT_OFFSET,
|
|
0x0f71usize,
|
|
4usize,
|
|
),
|
|
];
|
|
|
|
let grounded_field_observations = exact_fields
|
|
.iter()
|
|
.map(
|
|
|(field_name, runtime_object_offset, file_offset, field_width_bytes)| {
|
|
let raw = &bytes[*file_offset..*file_offset + *field_width_bytes];
|
|
let raw_hex = hex_encode(raw);
|
|
let (value_u8, value_u8_hex, value_u32, value_u32_hex, probable_f32_le) =
|
|
if *field_width_bytes == 1 {
|
|
let value = raw[0];
|
|
(
|
|
Some(value),
|
|
Some(format!("0x{value:02x}")),
|
|
None,
|
|
None,
|
|
None,
|
|
)
|
|
} else {
|
|
let value = u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]);
|
|
(
|
|
None,
|
|
None,
|
|
Some(value),
|
|
Some(format!("0x{value:08x}")),
|
|
probable_normal_f32_string(value),
|
|
)
|
|
};
|
|
SmpPostTextGroundedFieldObservation {
|
|
field_name: (*field_name).to_string(),
|
|
runtime_object_offset: *runtime_object_offset,
|
|
runtime_object_offset_hex: format!("0x{runtime_object_offset:04x}"),
|
|
file_offset: *file_offset,
|
|
file_offset_hex: format!("0x{file_offset:04x}"),
|
|
field_width_bytes: *field_width_bytes,
|
|
field_width_bytes_hex: format!("0x{field_width_bytes:x}"),
|
|
raw_hex,
|
|
value_u8,
|
|
value_u8_hex,
|
|
value_u32,
|
|
value_u32_hex,
|
|
probable_f32_le,
|
|
}
|
|
},
|
|
)
|
|
.collect::<Vec<_>>();
|
|
|
|
let one_byte_early_float_candidates = exact_fields
|
|
.iter()
|
|
.filter(|(_, _, file_offset, _)| *file_offset > 0)
|
|
.filter_map(|(field_name, runtime_object_offset, file_offset, _)| {
|
|
let candidate_offset = file_offset - 1;
|
|
let value = read_u32_at(bytes, candidate_offset)?;
|
|
let probable_f32_le = probable_normal_f32_string(value)?;
|
|
Some(SmpPostTextFloatAlignmentCandidate {
|
|
grounded_field_name: (*field_name).to_string(),
|
|
grounded_field_runtime_object_offset: *runtime_object_offset,
|
|
grounded_field_runtime_object_offset_hex: format!("0x{runtime_object_offset:04x}"),
|
|
grounded_field_file_offset: *file_offset,
|
|
grounded_field_file_offset_hex: format!("0x{file_offset:04x}"),
|
|
candidate_offset,
|
|
candidate_offset_hex: format!("0x{candidate_offset:04x}"),
|
|
candidate_value: value,
|
|
candidate_value_hex: format!("0x{value:08x}"),
|
|
probable_f32_le,
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let mut evidence = vec![
|
|
format!(
|
|
"post-text grounded-field neighborhood spans file offsets 0x{POST_TEXT_FIELD_NEIGHBORHOOD_OFFSET:04x}..0x{POST_TEXT_FIELD_NEIGHBORHOOD_END_OFFSET:04x}"
|
|
),
|
|
"this neighborhood starts at the first grounded post-text field [world+0x4c74] and extends through the later dword at [world+0x4c8c]".to_string(),
|
|
"the exact grounded field offsets here are byte-oriented at 0x0f59, 0x0f5d, 0x0f61, and 0x0f6d, with dword-sized fields only at 0x0f65 and 0x0f71".to_string(),
|
|
];
|
|
if one_byte_early_float_candidates.is_empty() {
|
|
evidence.push(
|
|
"no one-byte-early little-endian float-looking starts were observed ahead of the grounded fields in this file".to_string(),
|
|
);
|
|
} else {
|
|
evidence.push(format!(
|
|
"observed {} float-looking 4-byte starts exactly one byte before grounded field offsets in this file",
|
|
one_byte_early_float_candidates.len()
|
|
));
|
|
}
|
|
|
|
Some(SmpPostTextFieldNeighborhoodProbe {
|
|
profile_family,
|
|
source_kind,
|
|
window_offset: POST_TEXT_FIELD_NEIGHBORHOOD_OFFSET,
|
|
window_end_offset: POST_TEXT_FIELD_NEIGHBORHOOD_END_OFFSET,
|
|
window_len: POST_TEXT_FIELD_NEIGHBORHOOD_END_OFFSET - POST_TEXT_FIELD_NEIGHBORHOOD_OFFSET,
|
|
window_len_hex: format!(
|
|
"0x{:x}",
|
|
POST_TEXT_FIELD_NEIGHBORHOOD_END_OFFSET - POST_TEXT_FIELD_NEIGHBORHOOD_OFFSET
|
|
),
|
|
grounded_field_observations,
|
|
one_byte_early_float_candidates,
|
|
evidence,
|
|
})
|
|
}
|
|
|
|
fn parse_locomotive_policy_neighborhood_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
special_conditions_probe: Option<&SmpSpecialConditionsProbe>,
|
|
) -> Option<SmpLocomotivePolicyNeighborhoodProbe> {
|
|
special_conditions_probe?;
|
|
if LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET > bytes.len() {
|
|
return None;
|
|
}
|
|
|
|
let profile_family = container_profile
|
|
.map(|profile| profile.profile_family.clone())
|
|
.unwrap_or_else(|| "unknown".to_string());
|
|
let source_kind = match file_extension_hint.unwrap_or("") {
|
|
"gmp" => "locomotive-policy-neighborhood",
|
|
"gms" => "locomotive-policy-neighborhood",
|
|
"gmx" => "locomotive-policy-neighborhood",
|
|
_ => "locomotive-policy-neighborhood",
|
|
}
|
|
.to_string();
|
|
|
|
let exact_fields = [
|
|
(
|
|
"selected-year bucket companion scalar",
|
|
LOCOMOTIVE_POLICY_FIELD_NEG3_RUNTIME_OBJECT_OFFSET,
|
|
0x0f87usize,
|
|
4usize,
|
|
),
|
|
(
|
|
"startup-dispatch reset-owned band at +0x4cae",
|
|
LOCOMOTIVE_POLICY_FIELD_NEG2_RUNTIME_OBJECT_OFFSET,
|
|
0x0f93usize,
|
|
4usize,
|
|
),
|
|
(
|
|
"startup-dispatch reset-owned band at +0x4cb2",
|
|
LOCOMOTIVE_POLICY_FIELD_NEG1_RUNTIME_OBJECT_OFFSET,
|
|
0x0f97usize,
|
|
4usize,
|
|
),
|
|
(
|
|
"linked-site removal follow-on gate",
|
|
LOCOMOTIVE_POLICY_FIELD_0_RUNTIME_OBJECT_OFFSET,
|
|
0x0f78usize,
|
|
1usize,
|
|
),
|
|
(
|
|
"All Steam Locos Avail.",
|
|
LOCOMOTIVE_POLICY_FIELD_1_RUNTIME_OBJECT_OFFSET,
|
|
0x0f7cusize,
|
|
1usize,
|
|
),
|
|
(
|
|
"All Diesel Locos Avail.",
|
|
LOCOMOTIVE_POLICY_FIELD_2_RUNTIME_OBJECT_OFFSET,
|
|
0x0f7dusize,
|
|
1usize,
|
|
),
|
|
(
|
|
"All Electric Locos Avail.",
|
|
LOCOMOTIVE_POLICY_FIELD_3_RUNTIME_OBJECT_OFFSET,
|
|
0x0f7eusize,
|
|
1usize,
|
|
),
|
|
(
|
|
"station-list selected station id",
|
|
LOCOMOTIVE_POLICY_FIELD_4_RUNTIME_OBJECT_OFFSET,
|
|
0x0f9fusize,
|
|
4usize,
|
|
),
|
|
(
|
|
"cached available-locomotive rating",
|
|
LOCOMOTIVE_POLICY_FIELD_5_RUNTIME_OBJECT_OFFSET,
|
|
0x0fa3usize,
|
|
4usize,
|
|
),
|
|
];
|
|
|
|
let grounded_field_observations = exact_fields
|
|
.iter()
|
|
.map(
|
|
|(field_name, runtime_object_offset, file_offset, field_width_bytes)| {
|
|
let raw = &bytes[*file_offset..*file_offset + *field_width_bytes];
|
|
let raw_hex = hex_encode(raw);
|
|
let (value_u8, value_u8_hex, value_u32, value_u32_hex, probable_f32_le) =
|
|
if *field_width_bytes == 1 {
|
|
let value = raw[0];
|
|
(
|
|
Some(value),
|
|
Some(format!("0x{value:02x}")),
|
|
None,
|
|
None,
|
|
None,
|
|
)
|
|
} else {
|
|
let value = u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]);
|
|
(
|
|
None,
|
|
None,
|
|
Some(value),
|
|
Some(format!("0x{value:08x}")),
|
|
probable_normal_f32_string(value),
|
|
)
|
|
};
|
|
SmpLocomotivePolicyFieldObservation {
|
|
field_name: (*field_name).to_string(),
|
|
runtime_object_offset: *runtime_object_offset,
|
|
runtime_object_offset_hex: format!("0x{runtime_object_offset:04x}"),
|
|
file_offset: *file_offset,
|
|
file_offset_hex: format!("0x{file_offset:04x}"),
|
|
field_width_bytes: *field_width_bytes,
|
|
field_width_bytes_hex: format!("0x{field_width_bytes:x}"),
|
|
raw_hex,
|
|
value_u8,
|
|
value_u8_hex,
|
|
value_u32,
|
|
value_u32_hex,
|
|
probable_f32_le,
|
|
}
|
|
},
|
|
)
|
|
.collect::<Vec<_>>();
|
|
|
|
let three_byte_early_float_candidates = exact_fields
|
|
.iter()
|
|
.filter(|(_, _, _, width)| *width == 4usize)
|
|
.filter_map(|(field_name, runtime_object_offset, file_offset, _)| {
|
|
let candidate_offset = file_offset.saturating_sub(3);
|
|
let value = read_u32_at(bytes, candidate_offset)?;
|
|
let probable_f32_le = probable_normal_f32_string(value)?;
|
|
Some(SmpLocomotivePolicyFloatAlignmentCandidate {
|
|
grounded_field_name: (*field_name).to_string(),
|
|
grounded_field_runtime_object_offset: *runtime_object_offset,
|
|
grounded_field_runtime_object_offset_hex: format!("0x{runtime_object_offset:04x}"),
|
|
grounded_field_file_offset: *file_offset,
|
|
grounded_field_file_offset_hex: format!("0x{file_offset:04x}"),
|
|
candidate_offset,
|
|
candidate_offset_hex: format!("0x{candidate_offset:04x}"),
|
|
candidate_value: value,
|
|
candidate_value_hex: format!("0x{value:08x}"),
|
|
probable_f32_le,
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
let mut evidence = vec![
|
|
format!(
|
|
"locomotive-policy neighborhood spans file offsets 0x{LOCOMOTIVE_POLICY_NEIGHBORHOOD_OFFSET:04x}..0x{LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET:04x}"
|
|
),
|
|
"this neighborhood covers the selected-year bucket companion scalar, two startup-reset-owned bands, the linked-site removal gate, the three locomotive-availability policy bytes, the station-list selected-station mirror, and the cached available-locomotive rating".to_string(),
|
|
"the exact byte policy lanes live at 0x0f78 and 0x0f7c..0x0f7e, while the earlier grounded dword starts map to 0x0f87, 0x0f93, and 0x0f97 and the later grounded dword starts map to 0x0f9f and 0x0fa3".to_string(),
|
|
];
|
|
if three_byte_early_float_candidates.is_empty() {
|
|
evidence.push(
|
|
"no three-byte-early little-endian float-looking starts were observed ahead of the grounded dword fields in this file".to_string(),
|
|
);
|
|
} else {
|
|
evidence.push(format!(
|
|
"observed {} float-looking 4-byte starts exactly three bytes before grounded dword fields in this file",
|
|
three_byte_early_float_candidates.len()
|
|
));
|
|
}
|
|
|
|
Some(SmpLocomotivePolicyNeighborhoodProbe {
|
|
profile_family,
|
|
source_kind,
|
|
window_offset: LOCOMOTIVE_POLICY_NEIGHBORHOOD_OFFSET,
|
|
window_end_offset: LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET,
|
|
window_len: LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET
|
|
- LOCOMOTIVE_POLICY_NEIGHBORHOOD_OFFSET,
|
|
window_len_hex: format!(
|
|
"0x{:x}",
|
|
LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET - LOCOMOTIVE_POLICY_NEIGHBORHOOD_OFFSET
|
|
),
|
|
grounded_field_observations,
|
|
three_byte_early_float_candidates,
|
|
evidence,
|
|
})
|
|
}
|
|
|
|
fn parse_pre_recipe_scalar_plateau_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
special_conditions_probe: Option<&SmpSpecialConditionsProbe>,
|
|
) -> Option<SmpPreRecipeScalarPlateauProbe> {
|
|
special_conditions_probe?;
|
|
if PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET > bytes.len() {
|
|
return None;
|
|
}
|
|
|
|
let profile_family = container_profile
|
|
.map(|profile| profile.profile_family.clone())
|
|
.unwrap_or_else(|| "unknown".to_string());
|
|
let source_kind = match file_extension_hint.unwrap_or("") {
|
|
"gmp" => "pre-recipe-scalar-plateau",
|
|
"gms" => "pre-recipe-scalar-plateau",
|
|
"gmx" => "pre-recipe-scalar-plateau",
|
|
_ => "pre-recipe-scalar-plateau",
|
|
}
|
|
.to_string();
|
|
|
|
let aligned_dword_count =
|
|
(PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET - PRE_RECIPE_SCALAR_PLATEAU_OFFSET) / 4;
|
|
let mut nonzero_lanes = Vec::new();
|
|
for index in 0..aligned_dword_count {
|
|
let absolute_offset = PRE_RECIPE_SCALAR_PLATEAU_OFFSET + index * 4;
|
|
let value = read_u32_at(bytes, absolute_offset)?;
|
|
if value == 0 {
|
|
continue;
|
|
}
|
|
nonzero_lanes.push(SmpPreRecipeScalarPlateauLane {
|
|
absolute_offset,
|
|
relative_offset: absolute_offset - PRE_RECIPE_SCALAR_PLATEAU_OFFSET,
|
|
absolute_offset_hex: format!("0x{absolute_offset:04x}"),
|
|
relative_offset_hex: format!(
|
|
"0x{:x}",
|
|
absolute_offset - PRE_RECIPE_SCALAR_PLATEAU_OFFSET
|
|
),
|
|
value,
|
|
value_hex: format!("0x{value:08x}"),
|
|
probable_f32_le: probable_normal_f32_string(value),
|
|
});
|
|
}
|
|
|
|
let family_signature = match (
|
|
read_u32_at(bytes, 0x0faf),
|
|
read_u32_at(bytes, 0x0fb3),
|
|
read_u32_at(bytes, 0x0fcb),
|
|
) {
|
|
(Some(0x4000003f), Some(0xe560423f), Some(0x00000000)) => {
|
|
"rt3-105-scenario-pre-recipe-plateau-v1"
|
|
}
|
|
(Some(0x8000003f), Some(0x75c28f3f), Some(0x00300000)) => {
|
|
"rt3-105-base-pre-recipe-plateau-v1"
|
|
}
|
|
(Some(0x8000003f), Some(0x75c28f3f), Some(0xcdcdcd00)) => {
|
|
"rt3-105-alt-pre-recipe-plateau-v1"
|
|
}
|
|
_ => "unknown",
|
|
}
|
|
.to_string();
|
|
|
|
let mut evidence = vec![
|
|
format!(
|
|
"aligned scalar plateau spans file offsets 0x{PRE_RECIPE_SCALAR_PLATEAU_OFFSET:04x}..0x{PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET:04x}"
|
|
),
|
|
"this plateau ends immediately before the grounded recipe-book root at [world+0x0fe7]".to_string(),
|
|
"current grounding inside this span is still structural rather than semantic, so the probe only records aligned dword lanes and observed family signatures".to_string(),
|
|
];
|
|
if !nonzero_lanes.is_empty() {
|
|
evidence.push(format!(
|
|
"observed {} nonzero aligned dword lanes in the pre-recipe plateau",
|
|
nonzero_lanes.len()
|
|
));
|
|
}
|
|
if family_signature != "unknown" {
|
|
evidence.push(format!(
|
|
"matched observed family signature {family_signature}"
|
|
));
|
|
}
|
|
|
|
Some(SmpPreRecipeScalarPlateauProbe {
|
|
profile_family,
|
|
source_kind,
|
|
window_offset: PRE_RECIPE_SCALAR_PLATEAU_OFFSET,
|
|
window_end_offset: PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET,
|
|
window_len: PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET - PRE_RECIPE_SCALAR_PLATEAU_OFFSET,
|
|
window_len_hex: format!(
|
|
"0x{:x}",
|
|
PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET - PRE_RECIPE_SCALAR_PLATEAU_OFFSET
|
|
),
|
|
aligned_dword_count,
|
|
family_signature,
|
|
nonzero_lanes,
|
|
evidence,
|
|
})
|
|
}
|
|
|
|
fn parse_recipe_book_summary_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
special_conditions_probe: Option<&SmpSpecialConditionsProbe>,
|
|
) -> Option<SmpRecipeBookSummaryProbe> {
|
|
special_conditions_probe?;
|
|
if RECIPE_BOOK_SUMMARY_END_OFFSET > bytes.len() {
|
|
return None;
|
|
}
|
|
|
|
let profile_family = container_profile
|
|
.map(|profile| profile.profile_family.clone())
|
|
.unwrap_or_else(|| "unknown".to_string());
|
|
let source_kind = match file_extension_hint.unwrap_or("") {
|
|
"gmp" => "recipe-book-summary",
|
|
"gms" => "recipe-book-summary",
|
|
"gmx" => "recipe-book-summary",
|
|
_ => "recipe-book-summary",
|
|
}
|
|
.to_string();
|
|
|
|
let mut books = Vec::with_capacity(RECIPE_BOOK_COUNT);
|
|
let mut mixed_head_count = 0usize;
|
|
let mut mixed_line_area_count = 0usize;
|
|
let mut cdcd_line_area_count = 0usize;
|
|
let mut zero_line_area_count = 0usize;
|
|
|
|
for book_index in 0..RECIPE_BOOK_COUNT {
|
|
let book_offset = RECIPE_BOOK_ROOT_OFFSET + book_index * RECIPE_BOOK_STRIDE;
|
|
let head = &bytes[book_offset..book_offset + RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET];
|
|
let line_area_offset = book_offset + RECIPE_BOOK_LINE_AREA_OFFSET;
|
|
let line_area = &bytes[line_area_offset..line_area_offset + RECIPE_BOOK_LINE_AREA_LEN];
|
|
let max_annual_production_offset = book_offset + RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET;
|
|
let max_annual_production_word = read_u32_at(bytes, max_annual_production_offset)?;
|
|
let mut lines = Vec::with_capacity(RECIPE_BOOK_LINE_COUNT);
|
|
for line_index in 0..RECIPE_BOOK_LINE_COUNT {
|
|
let line_offset = line_area_offset + line_index * RECIPE_BOOK_LINE_STRIDE;
|
|
let line = &bytes[line_offset..line_offset + RECIPE_BOOK_LINE_STRIDE];
|
|
let supplied_cargo_token_window = &line[0x08..0x20];
|
|
let demanded_cargo_token_window = &line[0x1c..0x30];
|
|
let mode_word = read_u32_at(bytes, line_offset)?;
|
|
let annual_amount_word = read_u32_at(bytes, line_offset + 0x04)?;
|
|
let supplied_cargo_token_word = read_u32_at(bytes, line_offset + 0x08)?;
|
|
let demanded_cargo_token_word = read_u32_at(bytes, line_offset + 0x1c)?;
|
|
lines.push(SmpRecipeBookLineSummary {
|
|
line_index,
|
|
line_offset,
|
|
line_offset_hex: format!("0x{line_offset:04x}"),
|
|
line_kind: classify_recipe_book_region_kind(line).to_string(),
|
|
line_signature_kind: classify_recipe_line_signature(
|
|
mode_word,
|
|
supplied_cargo_token_word,
|
|
demanded_cargo_token_word,
|
|
)
|
|
.to_string(),
|
|
imports_to_runtime_descriptor: mode_word != 0,
|
|
runtime_import_branch_kind: classify_recipe_runtime_import_branch(mode_word)
|
|
.to_string(),
|
|
line_nonzero_byte_count: line.iter().filter(|byte| **byte != 0).count(),
|
|
line_cdcd_byte_count: line.iter().filter(|byte| **byte == 0xcd).count(),
|
|
line_first_16_hex: hex_encode(&line[..RECIPE_BOOK_HEAD_SAMPLE_LEN.min(line.len())]),
|
|
mode_word_offset: line_offset,
|
|
mode_word_offset_hex: format!("0x{line_offset:04x}"),
|
|
mode_word,
|
|
mode_word_hex: format!("0x{mode_word:08x}"),
|
|
annual_amount_offset: line_offset + 0x04,
|
|
annual_amount_offset_hex: format!("0x{:04x}", line_offset + 0x04),
|
|
annual_amount_word,
|
|
annual_amount_word_hex: format!("0x{annual_amount_word:08x}"),
|
|
annual_amount_probable_f32_le: probable_normal_f32_string(annual_amount_word),
|
|
supplied_cargo_token_offset: line_offset + 0x08,
|
|
supplied_cargo_token_offset_hex: format!("0x{:04x}", line_offset + 0x08),
|
|
supplied_cargo_token_word,
|
|
supplied_cargo_token_word_hex: format!("0x{supplied_cargo_token_word:08x}"),
|
|
supplied_cargo_token_layout_kind: classify_recipe_token_layout(
|
|
supplied_cargo_token_word,
|
|
)
|
|
.to_string(),
|
|
supplied_cargo_token_window_hex: hex_encode(supplied_cargo_token_window),
|
|
supplied_cargo_token_window_ascii: ascii_preview(supplied_cargo_token_window),
|
|
supplied_cargo_token_active_in_runtime_import: mode_word != 0 && mode_word != 1,
|
|
supplied_cargo_token_probable_high16_ascii_stem:
|
|
probable_recipe_token_high16_ascii_stem(supplied_cargo_token_word),
|
|
demanded_cargo_token_offset: line_offset + 0x1c,
|
|
demanded_cargo_token_offset_hex: format!("0x{:04x}", line_offset + 0x1c),
|
|
demanded_cargo_token_word,
|
|
demanded_cargo_token_word_hex: format!("0x{demanded_cargo_token_word:08x}"),
|
|
demanded_cargo_token_layout_kind: classify_recipe_token_layout(
|
|
demanded_cargo_token_word,
|
|
)
|
|
.to_string(),
|
|
demanded_cargo_token_window_hex: hex_encode(demanded_cargo_token_window),
|
|
demanded_cargo_token_window_ascii: ascii_preview(demanded_cargo_token_window),
|
|
demanded_cargo_token_active_in_runtime_import: mode_word == 1 || mode_word == 3,
|
|
demanded_cargo_token_probable_high16_ascii_stem:
|
|
probable_recipe_token_high16_ascii_stem(demanded_cargo_token_word),
|
|
});
|
|
}
|
|
|
|
let head_kind = classify_recipe_book_region_kind(head).to_string();
|
|
let line_area_kind = classify_recipe_book_region_kind(line_area).to_string();
|
|
if head_kind == "mixed" {
|
|
mixed_head_count += 1;
|
|
}
|
|
match line_area_kind.as_str() {
|
|
"zero" => zero_line_area_count += 1,
|
|
"cdcd" => cdcd_line_area_count += 1,
|
|
_ => mixed_line_area_count += 1,
|
|
}
|
|
|
|
books.push(SmpRecipeBookSummaryBook {
|
|
book_index,
|
|
book_offset,
|
|
book_offset_hex: format!("0x{book_offset:04x}"),
|
|
head_kind,
|
|
head_nonzero_byte_count: head.iter().filter(|byte| **byte != 0).count(),
|
|
head_cdcd_byte_count: head.iter().filter(|byte| **byte == 0xcd).count(),
|
|
head_first_16_hex: hex_encode(&head[..RECIPE_BOOK_HEAD_SAMPLE_LEN.min(head.len())]),
|
|
max_annual_production_offset,
|
|
max_annual_production_offset_hex: format!("0x{max_annual_production_offset:04x}"),
|
|
max_annual_production_word,
|
|
max_annual_production_word_hex: format!("0x{max_annual_production_word:08x}"),
|
|
max_annual_production_probable_f32_le: probable_normal_f32_string(
|
|
max_annual_production_word,
|
|
),
|
|
line_area_offset,
|
|
line_area_offset_hex: format!("0x{line_area_offset:04x}"),
|
|
line_area_len: RECIPE_BOOK_LINE_AREA_LEN,
|
|
line_area_len_hex: format!("0x{:x}", RECIPE_BOOK_LINE_AREA_LEN),
|
|
line_area_kind,
|
|
line_area_nonzero_byte_count: line_area.iter().filter(|byte| **byte != 0).count(),
|
|
line_area_cdcd_byte_count: line_area.iter().filter(|byte| **byte == 0xcd).count(),
|
|
line_area_first_16_hex: hex_encode(
|
|
&line_area[..RECIPE_BOOK_HEAD_SAMPLE_LEN.min(line_area.len())],
|
|
),
|
|
lines,
|
|
});
|
|
}
|
|
|
|
let mut evidence = vec![
|
|
format!(
|
|
"grounded recipe-book root begins at file offset 0x{RECIPE_BOOK_ROOT_OFFSET:04x} and runtime offset [world+0x{RECIPE_BOOK_ROOT_OFFSET:04x}]"
|
|
),
|
|
format!(
|
|
"parsed {RECIPE_BOOK_COUNT} fixed books with stride 0x{RECIPE_BOOK_STRIDE:x}, shared cap lane at +0x{RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET:x}, and five line slots at +0x{RECIPE_BOOK_LINE_AREA_OFFSET:x} with stride 0x{RECIPE_BOOK_LINE_STRIDE:x}"
|
|
),
|
|
"this probe is structural only: it summarizes per-book heads plus five raw line records without decoding the mode or cargo-token semantics beyond the grounded offsets".to_string(),
|
|
];
|
|
evidence.push(format!(
|
|
"{mixed_head_count} books have mixed pre-line heads; line areas split into {zero_line_area_count} zero, {cdcd_line_area_count} cdcd, and {mixed_line_area_count} mixed books"
|
|
));
|
|
|
|
Some(SmpRecipeBookSummaryProbe {
|
|
profile_family,
|
|
source_kind,
|
|
root_offset: RECIPE_BOOK_ROOT_OFFSET,
|
|
root_offset_hex: format!("0x{RECIPE_BOOK_ROOT_OFFSET:04x}"),
|
|
runtime_object_root_offset: RECIPE_BOOK_ROOT_OFFSET,
|
|
runtime_object_root_offset_hex: format!("0x{RECIPE_BOOK_ROOT_OFFSET:04x}"),
|
|
book_count: RECIPE_BOOK_COUNT,
|
|
book_stride: RECIPE_BOOK_STRIDE,
|
|
book_stride_hex: format!("0x{:x}", RECIPE_BOOK_STRIDE),
|
|
max_annual_production_relative_offset: RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET,
|
|
max_annual_production_relative_offset_hex: format!(
|
|
"0x{:x}",
|
|
RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET
|
|
),
|
|
line_area_relative_offset: RECIPE_BOOK_LINE_AREA_OFFSET,
|
|
line_area_relative_offset_hex: format!("0x{:x}", RECIPE_BOOK_LINE_AREA_OFFSET),
|
|
line_count: RECIPE_BOOK_LINE_COUNT,
|
|
line_stride: RECIPE_BOOK_LINE_STRIDE,
|
|
line_stride_hex: format!("0x{:x}", RECIPE_BOOK_LINE_STRIDE),
|
|
books,
|
|
evidence,
|
|
})
|
|
}
|
|
|
|
fn parse_smp_aligned_runtime_rule_band_probe(
|
|
bytes: &[u8],
|
|
file_extension_hint: Option<&str>,
|
|
container_profile: Option<&SmpContainerProfile>,
|
|
special_conditions_probe: Option<&SmpSpecialConditionsProbe>,
|
|
) -> Option<SmpAlignedRuntimeRuleBandProbe> {
|
|
special_conditions_probe?;
|
|
if SMP_ALIGNED_RUNTIME_RULE_END_OFFSET > bytes.len() {
|
|
return None;
|
|
}
|
|
|
|
let source_kind = match file_extension_hint.unwrap_or("") {
|
|
"gmp" => "map-smp-aligned-runtime-rule-band",
|
|
"gms" => "save-smp-aligned-runtime-rule-band",
|
|
"gmx" => "sandbox-smp-aligned-runtime-rule-band",
|
|
_ => "smp-aligned-runtime-rule-band",
|
|
}
|
|
.to_string();
|
|
let profile_family = container_profile
|
|
.map(|profile| profile.profile_family.clone())
|
|
.unwrap_or_else(|| "unknown".to_string());
|
|
|
|
let mut nonzero_lanes = Vec::new();
|
|
for band_index in 0..SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT {
|
|
let absolute_offset = SPECIAL_CONDITIONS_OFFSET + band_index * 4;
|
|
let value = read_u32_at(bytes, absolute_offset)?;
|
|
if value == 0 {
|
|
continue;
|
|
}
|
|
let lane_kind = if band_index < SPECIAL_CONDITION_COUNT {
|
|
"known-special-condition-dword"
|
|
} else if band_index < SMP_ALIGNED_RUNTIME_RULE_KNOWN_EDITOR_RULE_COUNT {
|
|
"unlabeled-editor-rule-dword"
|
|
} else {
|
|
"trailing-runtime-scalar"
|
|
}
|
|
.to_string();
|
|
let known_label = if band_index < SPECIAL_CONDITION_COUNT {
|
|
Some(
|
|
KNOWN_SPECIAL_CONDITION_DEFINITIONS[band_index]
|
|
.label
|
|
.to_string(),
|
|
)
|
|
} else {
|
|
None
|
|
};
|
|
nonzero_lanes.push(SmpAlignedRuntimeRuleBandLane {
|
|
band_index,
|
|
absolute_offset,
|
|
relative_offset: absolute_offset - SPECIAL_CONDITIONS_OFFSET,
|
|
absolute_offset_hex: format!("0x{absolute_offset:04x}"),
|
|
relative_offset_hex: format!("0x{:x}", absolute_offset - SPECIAL_CONDITIONS_OFFSET),
|
|
lane_kind,
|
|
known_label,
|
|
value,
|
|
value_hex: format!("0x{value:08x}"),
|
|
probable_f32_le: probable_normal_f32_string(value),
|
|
});
|
|
}
|
|
|
|
let nonzero_band_indices = nonzero_lanes
|
|
.iter()
|
|
.map(|lane| lane.band_index)
|
|
.collect::<Vec<_>>();
|
|
let nonzero_post_window_overlap_band_indices = nonzero_lanes
|
|
.iter()
|
|
.filter(|lane| {
|
|
lane.band_index >= SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX
|
|
&& lane.band_index
|
|
< SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX
|
|
+ SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT
|
|
})
|
|
.map(|lane| lane.band_index)
|
|
.collect::<Vec<_>>();
|
|
let nonzero_post_window_overlap_post_relative_offset_hexes = nonzero_lanes
|
|
.iter()
|
|
.filter(|lane| {
|
|
lane.band_index >= SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX
|
|
&& lane.band_index
|
|
< SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX
|
|
+ SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT
|
|
})
|
|
.map(|lane| {
|
|
format!(
|
|
"0x{:x}",
|
|
(lane.band_index - SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX) * 4
|
|
)
|
|
})
|
|
.collect::<Vec<_>>();
|
|
let nonzero_relative_offset_hexes = nonzero_lanes
|
|
.iter()
|
|
.map(|lane| lane.relative_offset_hex.clone())
|
|
.collect::<Vec<_>>();
|
|
let mut evidence = vec![
|
|
format!(
|
|
"fixed `.smp`-aligned runtime-rule band at 0x{SPECIAL_CONDITIONS_OFFSET:04x}..0x{SMP_ALIGNED_RUNTIME_RULE_END_OFFSET:04x}"
|
|
),
|
|
format!(
|
|
"band spans {} known editor rule dwords plus one trailing scalar",
|
|
SMP_ALIGNED_RUNTIME_RULE_KNOWN_EDITOR_RULE_COUNT
|
|
),
|
|
"first 36 dwords overlap the older fixed matrix probe rooted at 0x0d64".to_string(),
|
|
format!(
|
|
"trailing band indices {}..{} alias the leading post-sentinel window offsets {}..{}",
|
|
SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX,
|
|
SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX
|
|
+ SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT
|
|
- 1,
|
|
"0x00",
|
|
format!(
|
|
"0x{:x}",
|
|
(SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT - 1) * 4
|
|
)
|
|
),
|
|
"band matches the grounded `.smp` save/load copy into `[world+0x4a7f..+0x4b43]`"
|
|
.to_string(),
|
|
];
|
|
if nonzero_lanes.is_empty() {
|
|
evidence
|
|
.push("all dwords in the aligned runtime-rule band are zero for this file".to_string());
|
|
} else {
|
|
evidence.push(format!(
|
|
"observed {} nonzero lanes at band indices {:?}",
|
|
nonzero_lanes.len(),
|
|
nonzero_band_indices
|
|
));
|
|
if !nonzero_post_window_overlap_band_indices.is_empty() {
|
|
evidence.push(format!(
|
|
"nonzero overlap lanes mirror post-window offsets {:?}",
|
|
nonzero_post_window_overlap_post_relative_offset_hexes
|
|
));
|
|
}
|
|
}
|
|
|
|
Some(SmpAlignedRuntimeRuleBandProbe {
|
|
profile_family,
|
|
source_kind,
|
|
band_offset: SPECIAL_CONDITIONS_OFFSET,
|
|
band_end_offset: SMP_ALIGNED_RUNTIME_RULE_END_OFFSET,
|
|
band_len: SMP_ALIGNED_RUNTIME_RULE_END_OFFSET - SPECIAL_CONDITIONS_OFFSET,
|
|
band_len_hex: format!(
|
|
"0x{:x}",
|
|
SMP_ALIGNED_RUNTIME_RULE_END_OFFSET - SPECIAL_CONDITIONS_OFFSET
|
|
),
|
|
dword_count: SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT,
|
|
known_editor_rule_dword_count: SMP_ALIGNED_RUNTIME_RULE_KNOWN_EDITOR_RULE_COUNT,
|
|
trailing_scalar_index: SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT - 1,
|
|
trailing_scalar_offset: SPECIAL_CONDITIONS_OFFSET
|
|
+ (SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT - 1) * 4,
|
|
trailing_scalar_offset_hex: format!(
|
|
"0x{:04x}",
|
|
SPECIAL_CONDITIONS_OFFSET + (SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT - 1) * 4
|
|
),
|
|
post_window_overlap_start_index: SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX,
|
|
post_window_overlap_dword_count: SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT,
|
|
post_window_overlap_end_index: SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX
|
|
+ SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT
|
|
- 1,
|
|
post_window_overlap_post_relative_offset_start_hex: "0x0".to_string(),
|
|
post_window_overlap_post_relative_offset_end_hex: format!(
|
|
"0x{:x}",
|
|
(SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT - 1) * 4
|
|
),
|
|
nonzero_post_window_overlap_band_indices,
|
|
nonzero_post_window_overlap_post_relative_offset_hexes,
|
|
nonzero_lane_count: nonzero_lanes.len(),
|
|
nonzero_band_indices,
|
|
nonzero_relative_offset_hexes,
|
|
nonzero_lanes,
|
|
evidence,
|
|
})
|
|
}
|
|
|
|
fn matches_candidate_availability_table_header(bytes: &[u8], header_offset: usize) -> bool {
|
|
matches!(
|
|
(
|
|
read_u32_at(bytes, header_offset + 0x08),
|
|
read_u32_at(bytes, header_offset + 0x0c),
|
|
read_u32_at(bytes, header_offset + 0x10),
|
|
read_u32_at(bytes, header_offset + 0x14),
|
|
read_u32_at(bytes, header_offset + 0x18),
|
|
read_u32_at(bytes, header_offset + 0x1c),
|
|
read_u32_at(bytes, header_offset + 0x20),
|
|
read_u32_at(bytes, header_offset + 0x24),
|
|
read_u32_at(bytes, header_offset + 0x28),
|
|
),
|
|
(
|
|
Some(0x0000332e),
|
|
Some(0x00000001),
|
|
Some(0x00000022),
|
|
Some(0x00000002),
|
|
Some(0x00000002),
|
|
Some(_),
|
|
Some(_),
|
|
Some(0x00000000),
|
|
Some(0x00000001),
|
|
)
|
|
)
|
|
}
|
|
|
|
fn classify_name_table_footer_progress_alignment(value: u32) -> Option<&'static str> {
|
|
match value {
|
|
0x32dc => Some(
|
|
"Footer progress word 0x000032dc matches the grounded late rehydrate progress id 0x32dc.",
|
|
),
|
|
0x3714 => Some(
|
|
"Footer progress word 0x00003714 matches the grounded late rehydrate progress id 0x3714.",
|
|
),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn parse_rt3_105_packed_profile_block(
|
|
bytes: &[u8],
|
|
packed_profile_offset: usize,
|
|
packed_profile_len: usize,
|
|
) -> Option<SmpRt3105PackedProfileBlock> {
|
|
let block_end = packed_profile_offset.checked_add(packed_profile_len)?;
|
|
if block_end > bytes.len() || packed_profile_len != 0x108 {
|
|
return None;
|
|
}
|
|
|
|
let leading_word_0 = read_u32_at(bytes, packed_profile_offset)?;
|
|
let trailing_zero_word_count_after_leading_word = (1..4)
|
|
.take_while(|index| {
|
|
read_u32_at(bytes, packed_profile_offset + (index * 4)).is_some_and(|word| word == 0)
|
|
})
|
|
.count();
|
|
let header_flag_word_3 = read_u32_at(bytes, packed_profile_offset + 0x0c)?;
|
|
let map_path_offset = 0x10usize;
|
|
let display_name_offset = 0x43usize;
|
|
let stable_nonzero_word_offsets = [0x00usize, 0x0c, 0x78, 0x7c, 0x80, 0x84];
|
|
let stable_nonzero_words = stable_nonzero_word_offsets
|
|
.iter()
|
|
.filter_map(|relative_offset| {
|
|
let value = read_u32_at(bytes, packed_profile_offset + relative_offset)?;
|
|
if value == 0 {
|
|
return None;
|
|
}
|
|
|
|
Some(SmpPackedProfileWordLane {
|
|
relative_offset: *relative_offset,
|
|
relative_offset_hex: format!("0x{relative_offset:02x}"),
|
|
value,
|
|
value_hex: format!("0x{value:08x}"),
|
|
})
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
Some(SmpRt3105PackedProfileBlock {
|
|
relative_len: packed_profile_len,
|
|
relative_len_hex: format!("0x{packed_profile_len:03x}"),
|
|
leading_word_0,
|
|
leading_word_0_hex: format!("0x{leading_word_0:08x}"),
|
|
trailing_zero_word_count_after_leading_word,
|
|
header_flag_word_3,
|
|
header_flag_word_3_hex: format!("0x{header_flag_word_3:08x}"),
|
|
map_path_offset,
|
|
map_path: read_c_string_in_range(bytes, packed_profile_offset + map_path_offset, block_end),
|
|
display_name_offset,
|
|
display_name: read_c_string_in_range(
|
|
bytes,
|
|
packed_profile_offset + display_name_offset,
|
|
block_end,
|
|
),
|
|
profile_byte_0x77: bytes[packed_profile_offset + 0x77],
|
|
profile_byte_0x77_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0x77]),
|
|
profile_byte_0x82: bytes[packed_profile_offset + 0x82],
|
|
profile_byte_0x82_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0x82]),
|
|
profile_byte_0x97: bytes[packed_profile_offset + 0x97],
|
|
profile_byte_0x97_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0x97]),
|
|
profile_byte_0xc5: bytes[packed_profile_offset + 0xc5],
|
|
profile_byte_0xc5_hex: format!("0x{:02x}", bytes[packed_profile_offset + 0xc5]),
|
|
stable_nonzero_words,
|
|
})
|
|
}
|
|
|
|
fn collect_runtime_post_span_header_candidates(
|
|
bytes: &[u8],
|
|
start: usize,
|
|
search_len: usize,
|
|
) -> Vec<SmpRuntimePostSpanHeaderCandidate> {
|
|
let end = bytes.len().min(start + search_len);
|
|
let mut offset = start & !0x3;
|
|
let mut candidates = Vec::new();
|
|
|
|
while offset + 16 <= end && candidates.len() < 8 {
|
|
if let Some(candidate) = build_runtime_post_span_header_candidate(bytes, offset) {
|
|
let mut cluster_end = offset + 4;
|
|
while cluster_end + 16 <= end
|
|
&& build_runtime_post_span_header_candidate(bytes, cluster_end).is_some()
|
|
{
|
|
cluster_end += 4;
|
|
}
|
|
candidates.push(candidate);
|
|
offset = cluster_end;
|
|
} else {
|
|
offset += 4;
|
|
}
|
|
}
|
|
|
|
candidates
|
|
}
|
|
|
|
fn build_runtime_post_span_header_candidate(
|
|
bytes: &[u8],
|
|
offset: usize,
|
|
) -> Option<SmpRuntimePostSpanHeaderCandidate> {
|
|
let words = read_u32_window(bytes, offset, 4);
|
|
if words.len() < 4 {
|
|
return None;
|
|
}
|
|
|
|
let dense_words = words
|
|
.iter()
|
|
.copied()
|
|
.filter(|word| (word & 0xffff) == 0 && (word >> 16) != 0)
|
|
.collect::<Vec<_>>();
|
|
if dense_words.len() < 3 {
|
|
return None;
|
|
}
|
|
|
|
let high_u16_words = words
|
|
.iter()
|
|
.map(|word| (word >> 16) as u16)
|
|
.collect::<Vec<_>>();
|
|
let mut grounded_alignments = Vec::new();
|
|
for high in &high_u16_words {
|
|
if let Some(alignment) = classify_runtime_post_span_high16_grounded_alignment(*high) {
|
|
if !grounded_alignments
|
|
.iter()
|
|
.any(|existing| existing == alignment)
|
|
{
|
|
grounded_alignments.push(alignment.to_string());
|
|
}
|
|
}
|
|
}
|
|
|
|
Some(SmpRuntimePostSpanHeaderCandidate {
|
|
offset,
|
|
hex_words: words.iter().map(|word| format!("0x{word:08x}")).collect(),
|
|
dense_word_count: dense_words.len(),
|
|
high_hex_words: high_u16_words
|
|
.iter()
|
|
.map(|word| format!("0x{word:04x}"))
|
|
.collect(),
|
|
high_u16_words,
|
|
grounded_alignments,
|
|
words,
|
|
})
|
|
}
|
|
|
|
fn classify_runtime_post_span_high16_grounded_alignment(high_u16: u16) -> Option<&'static str> {
|
|
match high_u16 {
|
|
0x32dc => Some(
|
|
"High-16 value 0x32dc matches the grounded late rehydrate progress id posted during world_entry_transition_and_runtime_bringup.",
|
|
),
|
|
0x3714 => Some(
|
|
"High-16 value 0x3714 matches the grounded late rehydrate progress id posted during world_entry_transition_and_runtime_bringup.",
|
|
),
|
|
0x3715 => Some(
|
|
"High-16 value 0x3715 matches the grounded late rehydrate progress id posted during world_entry_transition_and_runtime_bringup.",
|
|
),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
fn find_grounded_progress_high16_hits(
|
|
bytes: &[u8],
|
|
start: usize,
|
|
search_len: usize,
|
|
) -> Vec<String> {
|
|
let end = bytes.len().min(start + search_len);
|
|
let mut hits = Vec::new();
|
|
let mut offset = start & !0x3;
|
|
while offset + 4 <= end {
|
|
if let Some(word) = read_u32_at(bytes, offset) {
|
|
let high = (word >> 16) as u16;
|
|
if matches!(high, 0x32dc | 0x3714 | 0x3715) {
|
|
hits.push(format!("0x{high:04x}@0x{offset:08x}"));
|
|
}
|
|
}
|
|
offset += 4;
|
|
}
|
|
hits
|
|
}
|
|
|
|
fn parse_grounded_progress_hit_offset(hits: &[String], high_u16: u16) -> Option<usize> {
|
|
let needle = format!("0x{high_u16:04x}@0x");
|
|
let hit = hits.iter().find(|hit| hit.starts_with(&needle))?;
|
|
let offset_hex = hit.split("@0x").nth(1)?;
|
|
usize::from_str_radix(offset_hex, 16).ok()
|
|
}
|
|
|
|
fn collect_ascii_previews_in_range(
|
|
bytes: &[u8],
|
|
start: usize,
|
|
end: usize,
|
|
min_len: usize,
|
|
) -> Vec<SmpAsciiPreview> {
|
|
let mut previews = Vec::new();
|
|
let mut run_start = None;
|
|
let end = end.min(bytes.len());
|
|
|
|
for index in start..end {
|
|
let byte = bytes[index];
|
|
if is_ascii_preview_byte(byte) {
|
|
run_start.get_or_insert(index);
|
|
continue;
|
|
}
|
|
|
|
if let Some(current_start) = run_start.take() {
|
|
if index - current_start >= min_len {
|
|
previews.push(build_ascii_preview(bytes, current_start, index));
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(current_start) = run_start {
|
|
if end - current_start >= min_len {
|
|
previews.push(build_ascii_preview(bytes, current_start, end));
|
|
}
|
|
}
|
|
|
|
previews
|
|
}
|
|
|
|
fn find_c_string_with_suffix_in_range(
|
|
bytes: &[u8],
|
|
start: usize,
|
|
end: usize,
|
|
suffix: &str,
|
|
) -> Option<usize> {
|
|
let end = end.min(bytes.len());
|
|
let suffix = suffix.as_bytes();
|
|
let mut offset = start.min(end);
|
|
|
|
while offset < end {
|
|
if !is_ascii_preview_byte(bytes[offset]) {
|
|
offset += 1;
|
|
continue;
|
|
}
|
|
|
|
let run_start = offset;
|
|
while offset < end && is_ascii_preview_byte(bytes[offset]) {
|
|
offset += 1;
|
|
}
|
|
|
|
let run = &bytes[run_start..offset];
|
|
if run.ends_with(suffix) {
|
|
return Some(run_start);
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn read_c_string_in_range(bytes: &[u8], start: usize, end: usize) -> Option<String> {
|
|
if start >= end || start >= bytes.len() {
|
|
return None;
|
|
}
|
|
|
|
let end = end.min(bytes.len());
|
|
let mut cursor = start;
|
|
while cursor < end && bytes[cursor] != 0 {
|
|
cursor += 1;
|
|
}
|
|
if cursor == start {
|
|
return None;
|
|
}
|
|
|
|
std::str::from_utf8(&bytes[start..cursor])
|
|
.ok()
|
|
.map(ToString::to_string)
|
|
}
|
|
|
|
fn find_u16_le_offsets(bytes: &[u8], needle: u16) -> Vec<usize> {
|
|
let pattern = needle.to_le_bytes();
|
|
bytes
|
|
.windows(pattern.len())
|
|
.enumerate()
|
|
.filter_map(|(offset, window)| (window == pattern).then_some(offset))
|
|
.collect()
|
|
}
|
|
|
|
fn find_u32_le_offsets(bytes: &[u8], needle: u32) -> Vec<usize> {
|
|
let pattern = needle.to_le_bytes();
|
|
bytes
|
|
.windows(pattern.len())
|
|
.enumerate()
|
|
.filter_map(|(offset, window)| (window == pattern).then_some(offset))
|
|
.collect()
|
|
}
|
|
|
|
fn find_next_nonzero_offset(bytes: &[u8], start: usize) -> Option<usize> {
|
|
bytes
|
|
.iter()
|
|
.enumerate()
|
|
.skip(start)
|
|
.find_map(|(offset, byte)| (*byte != 0).then_some(offset))
|
|
}
|
|
|
|
fn find_zero_run(bytes: &[u8], start: usize, min_len: usize) -> Option<usize> {
|
|
let mut run_start = None;
|
|
let mut run_len = 0usize;
|
|
|
|
for (offset, byte) in bytes.iter().enumerate().skip(start) {
|
|
if *byte == 0 {
|
|
run_start.get_or_insert(offset);
|
|
run_len += 1;
|
|
if run_len >= min_len {
|
|
return run_start;
|
|
}
|
|
} else {
|
|
run_start = None;
|
|
run_len = 0;
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|
|
|
|
fn find_first_ascii_run(bytes: &[u8]) -> Option<SmpAsciiPreview> {
|
|
let mut start = None;
|
|
|
|
for (index, byte) in bytes.iter().copied().enumerate() {
|
|
if is_ascii_preview_byte(byte) {
|
|
start.get_or_insert(index);
|
|
continue;
|
|
}
|
|
|
|
if let Some(run_start) = start.take() {
|
|
if index - run_start >= MIN_ASCII_RUN_LEN {
|
|
return Some(build_ascii_preview(bytes, run_start, index));
|
|
}
|
|
}
|
|
}
|
|
|
|
start.and_then(|run_start| {
|
|
if bytes.len() - run_start >= MIN_ASCII_RUN_LEN {
|
|
Some(build_ascii_preview(bytes, run_start, bytes.len()))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
|
|
fn build_ascii_preview(bytes: &[u8], start: usize, end: usize) -> SmpAsciiPreview {
|
|
let byte_len = end - start;
|
|
let preview_bytes = &bytes[start..end];
|
|
let preview = String::from_utf8_lossy(
|
|
&preview_bytes[..preview_bytes.len().min(ASCII_PREVIEW_CHAR_LIMIT)],
|
|
)
|
|
.into_owned();
|
|
|
|
SmpAsciiPreview {
|
|
offset: start,
|
|
byte_len,
|
|
truncated: byte_len > ASCII_PREVIEW_CHAR_LIMIT,
|
|
preview,
|
|
}
|
|
}
|
|
|
|
fn is_ascii_preview_byte(byte: u8) -> bool {
|
|
matches!(byte, b' ' | b'\t' | b'\n' | b'\r' | 0x21..=0x7e)
|
|
}
|
|
|
|
fn read_u32_window(bytes: &[u8], offset: usize, count: usize) -> Vec<u32> {
|
|
let mut words = Vec::new();
|
|
let end = bytes.len().min(offset + count * 4);
|
|
for chunk in bytes[offset..end].chunks_exact(4) {
|
|
words.push(u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]));
|
|
}
|
|
words
|
|
}
|
|
|
|
fn read_u16_window(bytes: &[u8], offset: usize, count: usize) -> Vec<u16> {
|
|
let mut words = Vec::new();
|
|
let end = bytes.len().min(offset + count * 2);
|
|
for chunk in bytes[offset..end].chunks_exact(2) {
|
|
words.push(u16::from_le_bytes([chunk[0], chunk[1]]));
|
|
}
|
|
words
|
|
}
|
|
|
|
fn format_u16_word_signature(words: &[u16]) -> String {
|
|
words
|
|
.iter()
|
|
.map(|word| format!("0x{word:04x}"))
|
|
.collect::<Vec<_>>()
|
|
.join(", ")
|
|
}
|
|
|
|
fn compact_nondirect_signature_family(
|
|
grouped_marker_relative_offset: Option<usize>,
|
|
head_signature_words: &[u16],
|
|
post_group_signature_words: &[u16],
|
|
) -> String {
|
|
let grouped_marker_bucket = grouped_marker_relative_offset.unwrap_or(0);
|
|
let head_word_2 = head_signature_words.get(2).copied().unwrap_or_default();
|
|
let head_word_4 = head_signature_words.get(4).copied().unwrap_or_default();
|
|
let head_word_6 = head_signature_words.get(6).copied().unwrap_or_default();
|
|
let head_word_8 = head_signature_words.get(8).copied().unwrap_or_default();
|
|
let head_word_10 = head_signature_words.get(10).copied().unwrap_or_default();
|
|
let post_word_1 = post_group_signature_words
|
|
.get(1)
|
|
.copied()
|
|
.unwrap_or_default();
|
|
let post_word_3 = post_group_signature_words
|
|
.get(3)
|
|
.copied()
|
|
.unwrap_or_default();
|
|
let post_word_5 = post_group_signature_words
|
|
.get(5)
|
|
.copied()
|
|
.unwrap_or_default();
|
|
let post_word_7 = post_group_signature_words
|
|
.get(7)
|
|
.copied()
|
|
.unwrap_or_default();
|
|
|
|
format!(
|
|
"nondirect-ge{:02x}-h{:04x}-{:04x}-{:04x}-{:04x}-{:04x}-p{:04x}-{:04x}-{:04x}-{:04x}",
|
|
grouped_marker_bucket,
|
|
head_word_2,
|
|
head_word_4,
|
|
head_word_6,
|
|
head_word_8,
|
|
head_word_10,
|
|
post_word_1,
|
|
post_word_3,
|
|
post_word_5,
|
|
post_word_7,
|
|
)
|
|
}
|
|
|
|
fn read_u8_at(bytes: &[u8], offset: usize) -> Option<u8> {
|
|
bytes.get(offset).copied()
|
|
}
|
|
|
|
fn read_u16_at(bytes: &[u8], offset: usize) -> Option<u16> {
|
|
let chunk = bytes.get(offset..offset + 2)?;
|
|
Some(u16::from_le_bytes([chunk[0], chunk[1]]))
|
|
}
|
|
|
|
fn read_u32_at(bytes: &[u8], offset: usize) -> Option<u32> {
|
|
let chunk = bytes.get(offset..offset + 4)?;
|
|
Some(u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
|
|
}
|
|
|
|
fn read_i32_at(bytes: &[u8], offset: usize) -> Option<i32> {
|
|
let chunk = bytes.get(offset..offset + 4)?;
|
|
Some(i32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
|
|
}
|
|
|
|
fn read_i64_at(bytes: &[u8], offset: usize) -> Option<i64> {
|
|
let chunk = bytes.get(offset..offset + 8)?;
|
|
Some(i64::from_le_bytes([
|
|
chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
|
|
]))
|
|
}
|
|
|
|
fn read_u64_at(bytes: &[u8], offset: usize) -> Option<u64> {
|
|
let chunk = bytes.get(offset..offset + 8)?;
|
|
Some(u64::from_le_bytes([
|
|
chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
|
|
]))
|
|
}
|
|
|
|
fn read_f32_at(bytes: &[u8], offset: usize) -> Option<f32> {
|
|
let chunk = bytes.get(offset..offset + 4)?;
|
|
Some(f32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
|
|
}
|
|
|
|
fn read_f64_at(bytes: &[u8], offset: usize) -> Option<f64> {
|
|
let chunk = bytes.get(offset..offset + 8)?;
|
|
Some(f64::from_le_bytes([
|
|
chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6], chunk[7],
|
|
]))
|
|
}
|
|
|
|
fn read_ascii_c_string_at(bytes: &[u8], offset: usize, max_len: usize) -> Option<String> {
|
|
let chunk = bytes.get(offset..offset + max_len)?;
|
|
let nul_index = chunk.iter().position(|byte| *byte == 0).unwrap_or(max_len);
|
|
let text = std::str::from_utf8(&chunk[..nul_index])
|
|
.ok()?
|
|
.trim()
|
|
.to_string();
|
|
Some(text)
|
|
}
|
|
|
|
fn parse_nonzero_u32(bytes: &[u8], offset: usize) -> Option<Option<u32>> {
|
|
read_u32_at(bytes, offset).map(|value| (value != 0).then_some(value))
|
|
}
|
|
|
|
fn round_f64_to_i64(value: f64) -> Option<i64> {
|
|
if !value.is_finite() {
|
|
return None;
|
|
}
|
|
let rounded = value.round();
|
|
if rounded < i64::MIN as f64 || rounded > i64::MAX as f64 {
|
|
return None;
|
|
}
|
|
Some(rounded as i64)
|
|
}
|
|
|
|
fn probable_normal_f32_string(value: u32) -> Option<String> {
|
|
let exponent = (value >> 23) & 0xff;
|
|
if exponent == 0 || exponent == 0xff {
|
|
return None;
|
|
}
|
|
let scalar = f32::from_bits(value);
|
|
if !scalar.is_finite() {
|
|
return None;
|
|
}
|
|
Some(format!("{scalar:.6}"))
|
|
}
|
|
|
|
fn probable_recipe_token_high16_ascii_stem(value: u32) -> Option<String> {
|
|
if value & 0xffff != 0 {
|
|
return None;
|
|
}
|
|
let high = ((value >> 16) & 0xffff) as u16;
|
|
if high == 0 {
|
|
return None;
|
|
}
|
|
let low_byte = (high & 0x00ff) as u8;
|
|
let high_byte = (high >> 8) as u8;
|
|
if !low_byte.is_ascii_alphabetic() || !high_byte.is_ascii_alphabetic() {
|
|
return None;
|
|
}
|
|
Some(format!("{}{}", low_byte as char, high_byte as char))
|
|
}
|
|
|
|
fn classify_recipe_token_layout(value: u32) -> &'static str {
|
|
if value == 0 {
|
|
return "zero";
|
|
}
|
|
if probable_recipe_token_high16_ascii_stem(value).is_some() {
|
|
return "high16-ascii-stem";
|
|
}
|
|
if value & 0xffff == 0 {
|
|
return "high16-numeric";
|
|
}
|
|
if value >> 16 == 0 {
|
|
return "low16-marker";
|
|
}
|
|
"mixed"
|
|
}
|
|
|
|
fn classify_recipe_line_signature(
|
|
mode_word: u32,
|
|
supplied_cargo_token_word: u32,
|
|
demanded_cargo_token_word: u32,
|
|
) -> &'static str {
|
|
let supplied_layout = classify_recipe_token_layout(supplied_cargo_token_word);
|
|
let demanded_layout = classify_recipe_token_layout(demanded_cargo_token_word);
|
|
if mode_word == 0 && supplied_cargo_token_word == 0 && demanded_layout == "high16-numeric" {
|
|
return "demand-numeric-entry";
|
|
}
|
|
if mode_word == 0 && supplied_cargo_token_word == 0 && demanded_layout == "high16-ascii-stem" {
|
|
return "demand-stem-entry";
|
|
}
|
|
if mode_word == 0 && demanded_cargo_token_word == 0 && supplied_layout == "high16-numeric" {
|
|
return "supply-numeric-entry";
|
|
}
|
|
if mode_word != 0 && demanded_cargo_token_word == 0 && supplied_layout == "low16-marker" {
|
|
return "supply-marker-entry";
|
|
}
|
|
if mode_word == 0 && supplied_cargo_token_word == 0 && demanded_cargo_token_word == 0 {
|
|
return "zero";
|
|
}
|
|
"mixed"
|
|
}
|
|
|
|
fn classify_recipe_runtime_import_branch(mode_word: u32) -> &'static str {
|
|
if mode_word == 0 {
|
|
return "zero-mode-skipped";
|
|
}
|
|
if mode_word == 1 {
|
|
return "mode1-demand-branch";
|
|
}
|
|
if mode_word == 3 {
|
|
return "mode3-dual-branch";
|
|
}
|
|
"nonzero-supply-branch"
|
|
}
|
|
|
|
fn classify_recipe_book_region_kind(bytes: &[u8]) -> &'static str {
|
|
if bytes.iter().all(|byte| *byte == 0) {
|
|
"zero"
|
|
} else if bytes.iter().all(|byte| *byte == 0xcd) {
|
|
"cdcd"
|
|
} else {
|
|
"mixed"
|
|
}
|
|
}
|
|
|
|
fn hex_encode(bytes: &[u8]) -> String {
|
|
bytes.iter().map(|byte| format!("{byte:02x}")).collect()
|
|
}
|
|
|
|
fn ascii_preview(bytes: &[u8]) -> String {
|
|
bytes
|
|
.iter()
|
|
.map(|byte| match byte {
|
|
0x20..=0x7e => char::from(*byte),
|
|
_ => '.',
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn sha256_hex(bytes: &[u8]) -> String {
|
|
let digest = Sha256::digest(bytes);
|
|
format!("{digest:x}")
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn reports_grounded_tag_hits_and_offsets() {
|
|
let bytes = [
|
|
0x34, 0x12, 0x00, 0x00, 0xe0, 0x2e, 0x00, 0x00, 0x01, 0x00, 0x04, 0x00, 0x00, 0x80,
|
|
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x71, 0x07, 0x00, 0x00, 0x71, 0x07, 0x00, 0x00,
|
|
0x71, 0x07, 0x00, 0x00, 0xaa, 0xbb, 0x00, 0x00, 0xcc, 0xdd, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, b'H', b'e', b'l',
|
|
b'l', b'o', b' ', b'R', b'R', b'T', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x12, 0x34, 0x56, 0x78,
|
|
0x9a, 0xbc, 0xde, 0xf0, 0x00, 0x00, 0x00, 0x00, 0xee, 0x2c, 0x11, 0x51, 0x2d, 0x22,
|
|
0x71, 0x94, 0x33, 0x72, 0x94,
|
|
];
|
|
let report = inspect_smp_bytes(&bytes);
|
|
|
|
assert!(report.contains_grounded_runtime_tags);
|
|
assert_eq!(report.known_tag_hits.len(), 4);
|
|
assert_eq!(report.preamble.word_count, 16);
|
|
assert_eq!(report.preamble.words[0].value_le, 0x00001234);
|
|
let shared_header = report
|
|
.shared_header
|
|
.as_ref()
|
|
.expect("shared header should parse");
|
|
assert!(shared_header.matches_grounded_common_signature);
|
|
let header_variant = report
|
|
.header_variant_probe
|
|
.as_ref()
|
|
.expect("header variant probe should exist");
|
|
assert_eq!(header_variant.variant_family, "unknown");
|
|
assert!(!header_variant.is_known_family);
|
|
assert_eq!(shared_header.primary_family_tag, 0x00002ee0);
|
|
assert_eq!(
|
|
shared_header.payload_window_words_8_to_9,
|
|
vec![0x0000bbaa, 0x0000ddcc]
|
|
);
|
|
assert!(shared_header.reserved_words_10_to_14_all_zero);
|
|
assert_eq!(shared_header.final_flag_word, 0);
|
|
let ascii_run = report
|
|
.first_ascii_run
|
|
.as_ref()
|
|
.expect("ascii run should exist");
|
|
assert_eq!(ascii_run.offset, 67);
|
|
assert_eq!(ascii_run.byte_len, 9);
|
|
assert_eq!(ascii_run.preview, "Hello RRT");
|
|
let early_probe = report
|
|
.early_content_probe
|
|
.as_ref()
|
|
.expect("early content probe should exist");
|
|
assert_eq!(early_probe.first_post_text_nonzero_offset, 88);
|
|
assert_eq!(early_probe.zero_pad_after_text_len, 12);
|
|
assert_eq!(early_probe.first_post_text_block_len, 4);
|
|
assert_eq!(early_probe.first_post_text_block_hex, "11223344");
|
|
assert_eq!(early_probe.trailing_zero_pad_after_first_block_len, 16);
|
|
assert_eq!(early_probe.secondary_nonzero_offset, Some(108));
|
|
assert_eq!(early_probe.secondary_aligned_word_window_offset, Some(108));
|
|
assert_eq!(
|
|
&early_probe.secondary_aligned_word_window_words[..2],
|
|
&[0x78563412, 0xf0debc9a]
|
|
);
|
|
assert!(
|
|
early_probe
|
|
.secondary_preview_hex
|
|
.starts_with("123456789abcdef0")
|
|
);
|
|
let secondary_variant = report
|
|
.secondary_variant_probe
|
|
.as_ref()
|
|
.expect("secondary variant probe should exist");
|
|
assert_eq!(secondary_variant.variant_family, "unknown");
|
|
let container_profile = report
|
|
.container_profile
|
|
.as_ref()
|
|
.expect("container profile should exist");
|
|
assert_eq!(container_profile.profile_family, "unknown");
|
|
assert!(!container_profile.is_known_profile);
|
|
assert!(report.save_bootstrap_block.is_none());
|
|
assert!(report.save_anchor_run_block.is_none());
|
|
assert!(report.runtime_anchor_cycle_block.is_none());
|
|
assert!(report.runtime_trailer_block.is_none());
|
|
assert!(report.runtime_post_span_probe.is_none());
|
|
assert!(report.classic_rehydrate_profile_probe.is_none());
|
|
assert_eq!(report.known_tag_hits[0].tag_id, 0x2cee);
|
|
assert_eq!(report.known_tag_hits[0].hit_count, 1);
|
|
assert_eq!(report.known_tag_hits[0].sample_offsets, vec![120]);
|
|
assert_eq!(report.known_tag_hits[1].tag_id, 0x2d51);
|
|
assert_eq!(report.known_tag_hits[1].sample_offsets, vec![123]);
|
|
assert_eq!(report.known_tag_hits[2].tag_id, 0x9471);
|
|
assert_eq!(report.known_tag_hits[2].sample_offsets, vec![126]);
|
|
assert_eq!(report.known_tag_hits[3].tag_id, 0x9472);
|
|
assert_eq!(report.known_tag_hits[3].sample_offsets, vec![129]);
|
|
}
|
|
|
|
#[test]
|
|
fn warns_when_no_grounded_tags_are_present() {
|
|
let report = inspect_smp_bytes(&[0xaa, 0xbb, 0xcc]);
|
|
|
|
assert!(!report.contains_grounded_runtime_tags);
|
|
assert!(report.known_tag_hits.is_empty());
|
|
assert_eq!(report.preamble.word_count, 0);
|
|
assert!(report.shared_header.is_none());
|
|
assert!(report.header_variant_probe.is_none());
|
|
assert!(report.first_ascii_run.is_none());
|
|
assert!(report.early_content_probe.is_none());
|
|
assert!(report.secondary_variant_probe.is_none());
|
|
assert!(report.container_profile.is_none());
|
|
assert!(report.save_bootstrap_block.is_none());
|
|
assert!(report.save_anchor_run_block.is_none());
|
|
assert!(report.runtime_anchor_cycle_block.is_none());
|
|
assert!(report.runtime_trailer_block.is_none());
|
|
assert!(report.runtime_post_span_probe.is_none());
|
|
assert!(report.classic_rehydrate_profile_probe.is_none());
|
|
assert!(
|
|
report
|
|
.warnings
|
|
.iter()
|
|
.any(|warning| warning.contains("No grounded runtime bundle tags were found"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_zeroed_post_special_conditions_scalar_window() {
|
|
let mut bytes = vec![0u8; POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET];
|
|
let sentinel_offset =
|
|
SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4;
|
|
bytes[sentinel_offset..sentinel_offset + 4].copy_from_slice(&1u32.to_le_bytes());
|
|
|
|
let special_conditions_probe = parse_special_conditions_probe(&bytes, Some("gmp"), None)
|
|
.expect("special-conditions probe should parse");
|
|
let probe = parse_post_special_conditions_scalar_probe(
|
|
&bytes,
|
|
Some("gmp"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-map-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
Some(&special_conditions_probe),
|
|
)
|
|
.expect("post-special-conditions probe should parse");
|
|
|
|
assert_eq!(probe.window_offset, POST_SPECIAL_CONDITIONS_SCALAR_OFFSET);
|
|
assert_eq!(
|
|
probe.window_end_offset,
|
|
POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET
|
|
);
|
|
assert_eq!(probe.dword_count, 79);
|
|
assert_eq!(
|
|
probe.overlap_end_offset,
|
|
POST_SPECIAL_CONDITIONS_SCALAR_OVERLAP_END_OFFSET
|
|
);
|
|
assert_eq!(probe.overlap_dword_count, 14);
|
|
assert_eq!(probe.overlap_nonzero_dword_count, 0);
|
|
assert!(probe.overlap_nonzero_relative_offset_hexes.is_empty());
|
|
assert_eq!(
|
|
probe.tail_offset,
|
|
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_OFFSET
|
|
);
|
|
assert_eq!(probe.tail_dword_count, 65);
|
|
assert_eq!(
|
|
probe.tail_runtime_object_offset,
|
|
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_OFFSET
|
|
);
|
|
assert_eq!(
|
|
probe.tail_runtime_object_end_offset,
|
|
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_RUNTIME_OBJECT_END_OFFSET
|
|
);
|
|
assert!(!probe.tail_runtime_object_validated_byte_mirror);
|
|
assert_eq!(
|
|
probe.tail_grounded_live_field_offset,
|
|
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_OFFSET
|
|
);
|
|
assert_eq!(
|
|
probe.tail_grounded_live_field_copy_len,
|
|
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_LEN
|
|
);
|
|
assert_eq!(
|
|
probe.tail_grounded_live_field_copy_end_offset,
|
|
POST_SPECIAL_CONDITIONS_SCALAR_TAIL_GROUNDED_TEXT_FIELD_COPY_END_OFFSET
|
|
);
|
|
assert!(probe.tail_window_cuts_through_grounded_live_field);
|
|
assert_eq!(
|
|
probe.tail_grounded_live_field_remaining_file_window_offset,
|
|
POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET
|
|
);
|
|
assert_eq!(
|
|
probe.tail_grounded_live_field_remaining_file_window_len,
|
|
0x28
|
|
);
|
|
assert_eq!(
|
|
probe.tail_grounded_live_field_remaining_file_window_nonzero_byte_count,
|
|
0
|
|
);
|
|
assert_eq!(probe.tail_next_grounded_dword_field_offset_hex, "0x4c80");
|
|
assert_eq!(
|
|
probe.tail_next_grounded_dword_field_file_offset_hex,
|
|
"0x0f65"
|
|
);
|
|
assert_eq!(probe.tail_second_grounded_dword_field_offset_hex, "0x4c8c");
|
|
assert_eq!(
|
|
probe.tail_second_grounded_dword_field_file_offset_hex,
|
|
"0x0f71"
|
|
);
|
|
assert!(!probe.post_text_field_file_alignment_matches_grounded_dword_fields);
|
|
assert!(
|
|
probe
|
|
.tail_grounded_live_field_remaining_file_window_first_nonzero_offset
|
|
.is_none()
|
|
);
|
|
assert!(
|
|
probe
|
|
.tail_grounded_live_field_remaining_file_window_last_nonzero_offset
|
|
.is_none()
|
|
);
|
|
assert_eq!(probe.tail_nonzero_dword_count, 0);
|
|
assert!(probe.tail_first_nonzero_offset.is_none());
|
|
assert!(probe.tail_last_nonzero_offset.is_none());
|
|
assert!(probe.tail_nonzero_relative_offset_hexes.is_empty());
|
|
assert_eq!(probe.nonzero_dword_count, 0);
|
|
assert!(probe.first_nonzero_offset.is_none());
|
|
assert!(probe.last_nonzero_offset.is_none());
|
|
assert!(probe.nonzero_lanes.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn parses_zeroed_smp_aligned_runtime_rule_band() {
|
|
let mut bytes = vec![0u8; POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET];
|
|
let sentinel_offset =
|
|
SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4;
|
|
bytes[sentinel_offset..sentinel_offset + 4].copy_from_slice(&1u32.to_le_bytes());
|
|
|
|
let special_conditions_probe = parse_special_conditions_probe(&bytes, Some("gmp"), None)
|
|
.expect("special-conditions probe should parse");
|
|
let probe = parse_smp_aligned_runtime_rule_band_probe(
|
|
&bytes,
|
|
Some("gmp"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-map-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
Some(&special_conditions_probe),
|
|
)
|
|
.expect("aligned runtime-rule band probe should parse");
|
|
|
|
assert_eq!(probe.band_offset, SPECIAL_CONDITIONS_OFFSET);
|
|
assert_eq!(probe.band_end_offset, SMP_ALIGNED_RUNTIME_RULE_END_OFFSET);
|
|
assert_eq!(probe.dword_count, SMP_ALIGNED_RUNTIME_RULE_DWORD_COUNT);
|
|
assert_eq!(
|
|
probe.known_editor_rule_dword_count,
|
|
SMP_ALIGNED_RUNTIME_RULE_KNOWN_EDITOR_RULE_COUNT
|
|
);
|
|
assert_eq!(
|
|
probe.post_window_overlap_start_index,
|
|
SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_START_INDEX
|
|
);
|
|
assert_eq!(
|
|
probe.post_window_overlap_dword_count,
|
|
SMP_ALIGNED_RUNTIME_RULE_POST_WINDOW_OVERLAP_DWORD_COUNT
|
|
);
|
|
assert_eq!(probe.nonzero_lane_count, 1);
|
|
assert_eq!(probe.nonzero_band_indices, vec![35]);
|
|
assert!(probe.nonzero_post_window_overlap_band_indices.is_empty());
|
|
assert!(
|
|
probe
|
|
.nonzero_post_window_overlap_post_relative_offset_hexes
|
|
.is_empty()
|
|
);
|
|
assert_eq!(
|
|
probe.nonzero_lanes[0].lane_kind,
|
|
"known-special-condition-dword"
|
|
);
|
|
assert_eq!(
|
|
probe.nonzero_lanes[0].known_label.as_deref(),
|
|
Some("Hidden sentinel")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_nonzero_post_special_conditions_scalar_window() {
|
|
let mut bytes = vec![0u8; POST_SPECIAL_CONDITIONS_GROUNDED_TEXT_FIELD_FILE_END_OFFSET];
|
|
let sentinel_offset =
|
|
SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4;
|
|
bytes[sentinel_offset..sentinel_offset + 4].copy_from_slice(&1u32.to_le_bytes());
|
|
bytes[0x0df8..0x0dfc].copy_from_slice(&0x413d298cu32.to_le_bytes());
|
|
bytes[0x0e00..0x0e04].copy_from_slice(&0x40e6b756u32.to_le_bytes());
|
|
bytes[0x0f0c..0x0f10].copy_from_slice(&0x42574909u32.to_le_bytes());
|
|
bytes[0x0f34] = 0xaa;
|
|
bytes[0x0f4e] = 0xbb;
|
|
|
|
let special_conditions_probe = parse_special_conditions_probe(&bytes, Some("gms"), None)
|
|
.expect("special-conditions probe should parse");
|
|
let probe = parse_post_special_conditions_scalar_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
Some(&special_conditions_probe),
|
|
)
|
|
.expect("post-special-conditions probe should parse");
|
|
|
|
assert_eq!(probe.nonzero_dword_count, 3);
|
|
assert_eq!(probe.first_nonzero_offset, Some(0x0df8));
|
|
assert_eq!(probe.last_nonzero_offset, Some(0x0f0c));
|
|
assert_eq!(probe.overlap_nonzero_dword_count, 2);
|
|
assert_eq!(
|
|
probe.overlap_nonzero_relative_offset_hexes,
|
|
vec!["0x4".to_string(), "0xc".to_string()]
|
|
);
|
|
assert_eq!(probe.tail_nonzero_dword_count, 1);
|
|
assert_eq!(probe.tail_first_nonzero_offset, Some(0x0f0c));
|
|
assert_eq!(probe.tail_last_nonzero_offset, Some(0x0f0c));
|
|
assert_eq!(
|
|
probe.tail_nonzero_relative_offset_hexes,
|
|
vec!["0x118".to_string()]
|
|
);
|
|
assert_eq!(probe.tail_runtime_object_offset_hex, "0x4b47");
|
|
assert_eq!(probe.tail_runtime_object_end_offset_hex, "0x4c4b");
|
|
assert_eq!(
|
|
probe.tail_grounded_live_field_copy_end_offset_hex,
|
|
"0x4c73".to_string()
|
|
);
|
|
assert_eq!(
|
|
probe.tail_grounded_live_field_name,
|
|
"victory-or-outcome status text buffer"
|
|
);
|
|
assert!(probe.tail_window_cuts_through_grounded_live_field);
|
|
assert_eq!(
|
|
probe.tail_grounded_live_field_remaining_file_window_len_hex,
|
|
"0x28"
|
|
);
|
|
assert_eq!(
|
|
probe.tail_grounded_live_field_remaining_file_window_nonzero_byte_count,
|
|
2
|
|
);
|
|
assert_eq!(
|
|
probe.tail_grounded_live_field_remaining_file_window_first_nonzero_offset,
|
|
Some(0x0f34)
|
|
);
|
|
assert_eq!(
|
|
probe.tail_grounded_live_field_remaining_file_window_last_nonzero_offset,
|
|
Some(0x0f4e)
|
|
);
|
|
assert_eq!(probe.tail_next_grounded_dword_field_file_offset, 0x0f65);
|
|
assert_eq!(probe.tail_second_grounded_dword_field_file_offset, 0x0f71);
|
|
assert!(!probe.post_text_field_file_alignment_matches_grounded_dword_fields);
|
|
assert_eq!(probe.nonzero_lanes[0].relative_offset, 0x04);
|
|
assert_eq!(probe.nonzero_lanes[1].relative_offset, 0x0c);
|
|
assert_eq!(probe.nonzero_lanes[2].relative_offset, 0x118);
|
|
assert!(
|
|
probe
|
|
.nonzero_lanes
|
|
.iter()
|
|
.all(|lane| lane.probable_f32_le.is_some())
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_post_text_field_neighborhood_probe() {
|
|
let mut bytes = vec![0u8; POST_TEXT_FIELD_NEIGHBORHOOD_END_OFFSET];
|
|
let sentinel_offset =
|
|
SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4;
|
|
bytes[sentinel_offset..sentinel_offset + 4].copy_from_slice(&1u32.to_le_bytes());
|
|
bytes[0x0f59] = 0x01;
|
|
bytes[0x0f5d] = 0x02;
|
|
bytes[0x0f61] = 0x03;
|
|
bytes[0x0f6d] = 0x04;
|
|
bytes[0x0f5c..0x0f60].copy_from_slice(&0x40f33333u32.to_le_bytes());
|
|
bytes[0x0f6c..0x0f70].copy_from_slice(&0x40c08cfbu32.to_le_bytes());
|
|
|
|
let special_conditions_probe = parse_special_conditions_probe(&bytes, Some("gms"), None)
|
|
.expect("special-conditions probe should parse");
|
|
let probe = parse_post_text_field_neighborhood_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-scenario-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
Some(&special_conditions_probe),
|
|
)
|
|
.expect("post-text field neighborhood probe should parse");
|
|
|
|
assert_eq!(probe.window_offset, POST_TEXT_FIELD_NEIGHBORHOOD_OFFSET);
|
|
assert_eq!(
|
|
probe.window_end_offset,
|
|
POST_TEXT_FIELD_NEIGHBORHOOD_END_OFFSET
|
|
);
|
|
assert_eq!(probe.grounded_field_observations.len(), 6);
|
|
assert_eq!(
|
|
probe.grounded_field_observations[0].field_name,
|
|
"Auto-Show Grade During Track Lay"
|
|
);
|
|
assert_eq!(probe.grounded_field_observations[0].value_u8, Some(0x01));
|
|
assert_eq!(
|
|
probe.grounded_field_observations[3].field_name,
|
|
"leftover simulation time accumulator"
|
|
);
|
|
assert_eq!(probe.one_byte_early_float_candidates.len(), 2);
|
|
assert_eq!(
|
|
probe.one_byte_early_float_candidates[0].grounded_field_name,
|
|
"Starting Building Density Level"
|
|
);
|
|
assert_eq!(
|
|
probe.one_byte_early_float_candidates[0].candidate_offset_hex,
|
|
"0x0f5c"
|
|
);
|
|
assert_eq!(
|
|
probe.one_byte_early_float_candidates[1].grounded_field_name,
|
|
"selected-year lane snapshot"
|
|
);
|
|
assert_eq!(
|
|
probe.one_byte_early_float_candidates[1].candidate_offset_hex,
|
|
"0x0f6c"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_locomotive_policy_neighborhood_probe() {
|
|
let mut bytes = vec![0u8; LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET];
|
|
let sentinel_offset =
|
|
SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4;
|
|
bytes[sentinel_offset..sentinel_offset + 4].copy_from_slice(&1u32.to_le_bytes());
|
|
bytes[0x0f78] = 0x01;
|
|
bytes[0x0f7c] = 0x02;
|
|
bytes[0x0f7d] = 0x03;
|
|
bytes[0x0f7e] = 0x04;
|
|
bytes[0x0f9c..0x0fa0].copy_from_slice(&0x42c1c036u32.to_le_bytes());
|
|
bytes[0x0fa0..0x0fa4].copy_from_slice(&0x433a7abeu32.to_le_bytes());
|
|
|
|
let special_conditions_probe = parse_special_conditions_probe(&bytes, Some("gms"), None)
|
|
.expect("special-conditions probe should parse");
|
|
let probe = parse_locomotive_policy_neighborhood_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-scenario-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
Some(&special_conditions_probe),
|
|
)
|
|
.expect("locomotive policy neighborhood probe should parse");
|
|
|
|
assert_eq!(probe.window_offset, LOCOMOTIVE_POLICY_NEIGHBORHOOD_OFFSET);
|
|
assert_eq!(
|
|
probe.window_end_offset,
|
|
LOCOMOTIVE_POLICY_NEIGHBORHOOD_END_OFFSET
|
|
);
|
|
assert_eq!(probe.grounded_field_observations.len(), 9);
|
|
assert_eq!(
|
|
probe.grounded_field_observations[0].field_name,
|
|
"selected-year bucket companion scalar"
|
|
);
|
|
assert_eq!(
|
|
probe.grounded_field_observations[4].field_name,
|
|
"All Steam Locos Avail."
|
|
);
|
|
assert_eq!(probe.grounded_field_observations[4].value_u8, Some(0x02));
|
|
assert_eq!(
|
|
probe.grounded_field_observations[8].field_name,
|
|
"cached available-locomotive rating"
|
|
);
|
|
assert_eq!(probe.three_byte_early_float_candidates.len(), 2);
|
|
assert_eq!(
|
|
probe.three_byte_early_float_candidates[0].grounded_field_name,
|
|
"station-list selected station id"
|
|
);
|
|
assert_eq!(
|
|
probe.three_byte_early_float_candidates[0].candidate_offset_hex,
|
|
"0x0f9c"
|
|
);
|
|
assert_eq!(
|
|
probe.three_byte_early_float_candidates[1].grounded_field_name,
|
|
"cached available-locomotive rating"
|
|
);
|
|
assert_eq!(
|
|
probe.three_byte_early_float_candidates[1].candidate_offset_hex,
|
|
"0x0fa0"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_pre_recipe_scalar_plateau_probe() {
|
|
let mut bytes = vec![0u8; PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET];
|
|
let sentinel_offset =
|
|
SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4;
|
|
bytes[sentinel_offset..sentinel_offset + 4].copy_from_slice(&1u32.to_le_bytes());
|
|
bytes[0x0fa7..0x0fab].copy_from_slice(&0x82839300u32.to_le_bytes());
|
|
bytes[0x0fab..0x0faf].copy_from_slice(&0x948c9949u32.to_le_bytes());
|
|
bytes[0x0faf..0x0fb3].copy_from_slice(&0x8000003fu32.to_le_bytes());
|
|
bytes[0x0fb3..0x0fb7].copy_from_slice(&0x75c28f3fu32.to_le_bytes());
|
|
bytes[0x0fcb..0x0fcf].copy_from_slice(&0x00300000u32.to_le_bytes());
|
|
bytes[0x0fdb..0x0fdf].copy_from_slice(&0x00ffea22u32.to_le_bytes());
|
|
|
|
let special_conditions_probe = parse_special_conditions_probe(&bytes, Some("gms"), None)
|
|
.expect("special-conditions probe should parse");
|
|
let probe = parse_pre_recipe_scalar_plateau_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
Some(&special_conditions_probe),
|
|
)
|
|
.expect("pre-recipe scalar plateau probe should parse");
|
|
|
|
assert_eq!(probe.window_offset, PRE_RECIPE_SCALAR_PLATEAU_OFFSET);
|
|
assert_eq!(
|
|
probe.window_end_offset,
|
|
PRE_RECIPE_SCALAR_PLATEAU_END_OFFSET
|
|
);
|
|
assert_eq!(probe.family_signature, "rt3-105-base-pre-recipe-plateau-v1");
|
|
assert_eq!(probe.nonzero_lanes[0].absolute_offset_hex, "0x0fa7");
|
|
assert_eq!(probe.nonzero_lanes[2].absolute_offset_hex, "0x0faf");
|
|
assert_eq!(probe.nonzero_lanes[2].value_hex, "0x8000003f");
|
|
}
|
|
|
|
#[test]
|
|
fn parses_recipe_book_summary_probe() {
|
|
let mut bytes = vec![0u8; RECIPE_BOOK_SUMMARY_END_OFFSET];
|
|
let sentinel_offset =
|
|
SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4;
|
|
bytes[sentinel_offset..sentinel_offset + 4].copy_from_slice(&1u32.to_le_bytes());
|
|
|
|
let book0 = RECIPE_BOOK_ROOT_OFFSET;
|
|
bytes[book0..book0 + 16].copy_from_slice(&[
|
|
0x11, 0x22, 0x33, 0x44, 0xaa, 0xbb, 0xcc, 0xdd, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60,
|
|
0x70, 0x80,
|
|
]);
|
|
bytes[book0 + RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET
|
|
..book0 + RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET + 4]
|
|
.copy_from_slice(&0x41200000u32.to_le_bytes());
|
|
bytes[book0 + RECIPE_BOOK_LINE_AREA_OFFSET
|
|
..book0 + RECIPE_BOOK_LINE_AREA_OFFSET + RECIPE_BOOK_LINE_AREA_LEN]
|
|
.fill(0xcd);
|
|
bytes[book0 + RECIPE_BOOK_LINE_AREA_OFFSET..book0 + RECIPE_BOOK_LINE_AREA_OFFSET + 4]
|
|
.copy_from_slice(&0x00000003u32.to_le_bytes());
|
|
bytes[book0 + RECIPE_BOOK_LINE_AREA_OFFSET + 4..book0 + RECIPE_BOOK_LINE_AREA_OFFSET + 8]
|
|
.copy_from_slice(&0x41a00000u32.to_le_bytes());
|
|
bytes[book0 + RECIPE_BOOK_LINE_AREA_OFFSET + 8..book0 + RECIPE_BOOK_LINE_AREA_OFFSET + 12]
|
|
.copy_from_slice(&0x00000017u32.to_le_bytes());
|
|
bytes[book0 + RECIPE_BOOK_LINE_AREA_OFFSET + 0x1c
|
|
..book0 + RECIPE_BOOK_LINE_AREA_OFFSET + 0x20]
|
|
.copy_from_slice(&0x0000002au32.to_le_bytes());
|
|
|
|
let book1 = RECIPE_BOOK_ROOT_OFFSET + RECIPE_BOOK_STRIDE;
|
|
bytes[book1 + RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET
|
|
..book1 + RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET + 4]
|
|
.copy_from_slice(&0x00000000u32.to_le_bytes());
|
|
|
|
let special_conditions_probe = parse_special_conditions_probe(&bytes, Some("gmp"), None)
|
|
.expect("special-conditions probe should parse");
|
|
let probe = parse_recipe_book_summary_probe(
|
|
&bytes,
|
|
Some("gmp"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-map-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
Some(&special_conditions_probe),
|
|
)
|
|
.expect("recipe-book summary probe should parse");
|
|
|
|
assert_eq!(probe.root_offset, RECIPE_BOOK_ROOT_OFFSET);
|
|
assert_eq!(probe.book_count, RECIPE_BOOK_COUNT);
|
|
assert_eq!(probe.book_stride, RECIPE_BOOK_STRIDE);
|
|
assert_eq!(
|
|
probe.max_annual_production_relative_offset,
|
|
RECIPE_BOOK_MAX_ANNUAL_PRODUCTION_OFFSET
|
|
);
|
|
assert_eq!(probe.books[0].head_kind, "mixed");
|
|
assert_eq!(probe.books[0].line_area_kind, "mixed");
|
|
assert_eq!(probe.books[0].max_annual_production_word_hex, "0x41200000");
|
|
assert_eq!(
|
|
probe.books[0]
|
|
.max_annual_production_probable_f32_le
|
|
.as_deref(),
|
|
Some("10.000000")
|
|
);
|
|
assert_eq!(probe.books[0].lines.len(), RECIPE_BOOK_LINE_COUNT);
|
|
assert_eq!(probe.books[0].lines[0].line_kind, "mixed");
|
|
assert_eq!(probe.books[0].lines[0].mode_word_hex, "0x00000003");
|
|
assert_eq!(probe.books[0].lines[0].annual_amount_word_hex, "0x41a00000");
|
|
assert_eq!(
|
|
probe.books[0].lines[0]
|
|
.annual_amount_probable_f32_le
|
|
.as_deref(),
|
|
Some("20.000000")
|
|
);
|
|
assert_eq!(
|
|
probe.books[0].lines[0].supplied_cargo_token_word_hex,
|
|
"0x00000017"
|
|
);
|
|
assert_eq!(
|
|
probe.books[0].lines[0].supplied_cargo_token_window_hex,
|
|
"17000000cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd2a000000"
|
|
);
|
|
assert_eq!(
|
|
probe.books[0].lines[0].supplied_cargo_token_window_ascii,
|
|
"....................*..."
|
|
);
|
|
assert!(probe.books[0].lines[0].supplied_cargo_token_active_in_runtime_import);
|
|
assert_eq!(
|
|
probe.books[0].lines[0].demanded_cargo_token_word_hex,
|
|
"0x0000002a"
|
|
);
|
|
assert_eq!(
|
|
probe.books[0].lines[0].demanded_cargo_token_window_hex,
|
|
"2a000000cdcdcdcdcdcdcdcdcdcdcdcdcdcdcdcd"
|
|
);
|
|
assert_eq!(
|
|
probe.books[0].lines[0].demanded_cargo_token_window_ascii,
|
|
"*..................."
|
|
);
|
|
assert!(probe.books[0].lines[0].demanded_cargo_token_active_in_runtime_import);
|
|
assert_eq!(probe.books[1].head_kind, "zero");
|
|
assert_eq!(probe.books[1].line_area_kind, "zero");
|
|
assert_eq!(probe.books[1].lines[0].line_kind, "zero");
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_probable_recipe_token_high16_ascii_stem() {
|
|
assert_eq!(
|
|
probable_recipe_token_high16_ascii_stem(0x72470000).as_deref(),
|
|
Some("Gr")
|
|
);
|
|
assert_eq!(
|
|
probable_recipe_token_high16_ascii_stem(0x68430000).as_deref(),
|
|
Some("Ch")
|
|
);
|
|
assert_eq!(probable_recipe_token_high16_ascii_stem(0x000040a0), None);
|
|
assert_eq!(probable_recipe_token_high16_ascii_stem(0x00170000), None);
|
|
}
|
|
|
|
#[test]
|
|
fn classifies_recipe_token_layouts() {
|
|
assert_eq!(classify_recipe_token_layout(0x00000000), "zero");
|
|
assert_eq!(
|
|
classify_recipe_token_layout(0x72470000),
|
|
"high16-ascii-stem"
|
|
);
|
|
assert_eq!(classify_recipe_token_layout(0x00170000), "high16-numeric");
|
|
assert_eq!(classify_recipe_token_layout(0x000040a0), "low16-marker");
|
|
}
|
|
|
|
#[test]
|
|
fn classifies_recipe_line_signatures() {
|
|
assert_eq!(
|
|
classify_recipe_line_signature(0x00000000, 0x00000000, 0x00010000),
|
|
"demand-numeric-entry"
|
|
);
|
|
assert_eq!(
|
|
classify_recipe_line_signature(0x00000000, 0x00000000, 0x72470000),
|
|
"demand-stem-entry"
|
|
);
|
|
assert_eq!(
|
|
classify_recipe_line_signature(0x00000000, 0x00170000, 0x00000000),
|
|
"supply-numeric-entry"
|
|
);
|
|
assert_eq!(
|
|
classify_recipe_line_signature(0x00110000, 0x000040a0, 0x00000000),
|
|
"supply-marker-entry"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn classifies_recipe_runtime_import_branches() {
|
|
assert_eq!(
|
|
classify_recipe_runtime_import_branch(0),
|
|
"zero-mode-skipped"
|
|
);
|
|
assert_eq!(
|
|
classify_recipe_runtime_import_branch(1),
|
|
"mode1-demand-branch"
|
|
);
|
|
assert_eq!(
|
|
classify_recipe_runtime_import_branch(3),
|
|
"mode3-dual-branch"
|
|
);
|
|
assert_eq!(
|
|
classify_recipe_runtime_import_branch(0x00110000),
|
|
"nonzero-supply-branch"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_nonzero_smp_aligned_runtime_rule_band() {
|
|
let mut bytes = vec![0u8; POST_SPECIAL_CONDITIONS_SCALAR_END_OFFSET];
|
|
let sentinel_offset =
|
|
SPECIAL_CONDITIONS_OFFSET + SPECIAL_CONDITION_HIDDEN_SENTINEL_SLOT * 4;
|
|
bytes[sentinel_offset..sentinel_offset + 4].copy_from_slice(&1u32.to_le_bytes());
|
|
bytes[0x0df8..0x0dfc].copy_from_slice(&0x413d298cu32.to_le_bytes());
|
|
bytes[0x0e00..0x0e04].copy_from_slice(&0x40e6b756u32.to_le_bytes());
|
|
bytes[0x0e18..0x0e1c].copy_from_slice(&0x41d4ccceu32.to_le_bytes());
|
|
bytes[0x0e24..0x0e28].copy_from_slice(&0x3fd2b549u32.to_le_bytes());
|
|
|
|
let special_conditions_probe = parse_special_conditions_probe(&bytes, Some("gms"), None)
|
|
.expect("special-conditions probe should parse");
|
|
let probe = parse_smp_aligned_runtime_rule_band_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-scenario-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
Some(&special_conditions_probe),
|
|
)
|
|
.expect("aligned runtime-rule band probe should parse");
|
|
|
|
assert_eq!(probe.nonzero_band_indices, vec![35, 37, 39, 45, 48]);
|
|
assert_eq!(
|
|
probe.nonzero_post_window_overlap_band_indices,
|
|
vec![37, 39, 45, 48]
|
|
);
|
|
assert_eq!(
|
|
probe.nonzero_post_window_overlap_post_relative_offset_hexes,
|
|
vec![
|
|
"0x4".to_string(),
|
|
"0xc".to_string(),
|
|
"0x24".to_string(),
|
|
"0x30".to_string()
|
|
]
|
|
);
|
|
assert_eq!(probe.nonzero_lanes[1].relative_offset, 0x94);
|
|
assert_eq!(
|
|
probe.nonzero_lanes[1].lane_kind,
|
|
"unlabeled-editor-rule-dword"
|
|
);
|
|
assert!(probe.nonzero_lanes[1].probable_f32_le.is_some());
|
|
assert_eq!(probe.nonzero_lanes.last().unwrap().band_index, 48);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_save_anchor_cycle_and_trailer() {
|
|
let cycle_words: [u32; 9] = [
|
|
0x00000000, 0x0186a000, 0x00000000, 0x86a00000, 0x00000001, 0xa0000000, 0x00000186,
|
|
0x00000000, 0x000186a0,
|
|
];
|
|
let trailer_words: [u32; 3] = [0x00020000, 0x00030000, 0x2ee10000];
|
|
let mut bytes = vec![0u8; 0x1c + (cycle_words.len() * 2 + 2 + trailer_words.len()) * 4];
|
|
|
|
let mut cursor = 0x1c;
|
|
for _ in 0..2 {
|
|
for word in cycle_words {
|
|
bytes[cursor..cursor + 4].copy_from_slice(&word.to_le_bytes());
|
|
cursor += 4;
|
|
}
|
|
}
|
|
for word in &cycle_words[..2] {
|
|
bytes[cursor..cursor + 4].copy_from_slice(&(*word).to_le_bytes());
|
|
cursor += 4;
|
|
}
|
|
for word in trailer_words {
|
|
bytes[cursor..cursor + 4].copy_from_slice(&word.to_le_bytes());
|
|
cursor += 4;
|
|
}
|
|
|
|
let container_profile = SmpContainerProfile {
|
|
profile_family: "rt3-classic-save-container-v1".to_string(),
|
|
profile_evidence: vec!["test".to_string()],
|
|
is_known_profile: true,
|
|
};
|
|
let bootstrap = SmpSaveBootstrapBlock {
|
|
profile_family: "rt3-classic-save-container-v1".to_string(),
|
|
aligned_window_offset: 0,
|
|
leading_word: 0,
|
|
leading_word_hex: "0x00000000".to_string(),
|
|
anchor_word: 0,
|
|
anchor_word_hex: "0x00000000".to_string(),
|
|
descriptor_word_2: 0,
|
|
descriptor_word_2_hex: "0x00000000".to_string(),
|
|
descriptor_word_3: 0,
|
|
descriptor_word_3_hex: "0x00000000".to_string(),
|
|
descriptor_word_4: 0,
|
|
descriptor_word_4_hex: "0x00000000".to_string(),
|
|
descriptor_word_5: 0,
|
|
descriptor_word_5_hex: "0x00000000".to_string(),
|
|
descriptor_word_6: 0,
|
|
descriptor_word_6_hex: "0x00000000".to_string(),
|
|
descriptor_word_7: 0,
|
|
descriptor_word_7_hex: "0x00000000".to_string(),
|
|
};
|
|
|
|
let parsed =
|
|
parse_save_anchor_run_block(&bytes, Some(&container_profile), Some(&bootstrap))
|
|
.expect("cycle block should parse");
|
|
|
|
assert_eq!(parsed.cycle_start_offset, 0x1c);
|
|
assert_eq!(parsed.cycle_words, cycle_words);
|
|
assert_eq!(parsed.full_cycle_count, 2);
|
|
assert_eq!(parsed.partial_cycle_word_count, 2);
|
|
assert_eq!(
|
|
parsed.trailer_offset,
|
|
0x1c + (cycle_words.len() * 2 + 2) * 4
|
|
);
|
|
assert_eq!(parsed.trailer_words, trailer_words);
|
|
}
|
|
|
|
#[test]
|
|
fn classifies_runtime_trailer_family() {
|
|
let runtime_anchor_cycle_block = SmpRuntimeAnchorCycleBlock {
|
|
profile_family: "rt3-classic-sandbox-container-v1".to_string(),
|
|
cycle_start_offset: 0x33c,
|
|
cycle_words: vec![0; 9],
|
|
cycle_hex_words: vec!["0x00000000".to_string(); 9],
|
|
full_cycle_count: 3,
|
|
partial_cycle_word_count: 2,
|
|
trailer_offset: 0x3b0,
|
|
trailer_words: vec![
|
|
0x00010000, 0x00010000, 0x00010000, 0x00010000, 0x00000000, 0x00000000, 0x2ee10000,
|
|
0x32c80000, 0x0dcd0000, 0x01010107, 0x26010000, 0x01010107, 0x00010000, 0x0334c68c,
|
|
0x03000000, 0x01000000,
|
|
],
|
|
trailer_hex_words: Vec::new(),
|
|
};
|
|
let container_profile = SmpContainerProfile {
|
|
profile_family: "rt3-classic-sandbox-container-v1".to_string(),
|
|
profile_evidence: vec!["test".to_string()],
|
|
is_known_profile: true,
|
|
};
|
|
|
|
let trailer = parse_runtime_trailer_block(
|
|
Some(&container_profile),
|
|
Some(&runtime_anchor_cycle_block),
|
|
)
|
|
.expect("runtime trailer should parse");
|
|
|
|
assert_eq!(trailer.trailer_family, "rt3-classic-sandbox-trailer-v1");
|
|
assert_eq!(trailer.prefix_words_0_to_5[0], 0x00010000);
|
|
assert_eq!(trailer.tag_word_6, 0x2ee10000);
|
|
assert_eq!(trailer.tag_chunk_id_u16, 0x2ee1);
|
|
assert_eq!(trailer.selector_word_8, 0x0dcd0000);
|
|
assert_eq!(trailer.selector_high_u16, 0x0dcd);
|
|
assert_eq!(trailer.mode_word_15, 0x01000000);
|
|
}
|
|
|
|
#[test]
|
|
fn probes_runtime_post_span_region() {
|
|
let mut bytes = vec![0u8; 0x200];
|
|
bytes[0x90..0x94].copy_from_slice(&0x32dc0000u32.to_le_bytes());
|
|
bytes[0x94..0x98].copy_from_slice(&0x37140000u32.to_le_bytes());
|
|
bytes[0x98..0x9c].copy_from_slice(&0x03000000u32.to_le_bytes());
|
|
bytes[0xa0..0xa4].copy_from_slice(&0x37150000u32.to_le_bytes());
|
|
bytes[0xa4..0xa8].copy_from_slice(&0x00010000u32.to_le_bytes());
|
|
bytes[0xa8..0xac].copy_from_slice(&0x00410000u32.to_le_bytes());
|
|
|
|
let trailer = SmpRuntimeTrailerBlock {
|
|
profile_family: "rt3-classic-save-container-v1".to_string(),
|
|
trailer_family: "test".to_string(),
|
|
trailer_evidence: Vec::new(),
|
|
trailer_offset: 0x40,
|
|
prefix_words_0_to_5: Vec::new(),
|
|
prefix_hex_words_0_to_5: Vec::new(),
|
|
tag_word_6: 0x2ee10000,
|
|
tag_word_6_hex: "0x2ee10000".to_string(),
|
|
tag_chunk_id_u16: 0x2ee1,
|
|
tag_chunk_id_hex: "0x2ee1".to_string(),
|
|
tag_chunk_id_grounded_alignment: None,
|
|
length_word_7: 0x00200000,
|
|
length_word_7_hex: "0x00200000".to_string(),
|
|
length_high_u16: 0x0020,
|
|
length_high_hex: "0x0020".to_string(),
|
|
selector_word_8: 0,
|
|
selector_word_8_hex: "0x00000000".to_string(),
|
|
selector_high_u16: 0,
|
|
selector_high_hex: "0x0000".to_string(),
|
|
layout_word_9: 0,
|
|
layout_word_9_hex: "0x00000000".to_string(),
|
|
descriptor_word_10: 0,
|
|
descriptor_word_10_hex: "0x00000000".to_string(),
|
|
descriptor_high_u16: 0,
|
|
descriptor_high_hex: "0x0000".to_string(),
|
|
descriptor_word_11: 0,
|
|
descriptor_word_11_hex: "0x00000000".to_string(),
|
|
counter_word_12: 0,
|
|
counter_word_12_hex: "0x00000000".to_string(),
|
|
offset_word_13: 0,
|
|
offset_word_13_hex: "0x00000000".to_string(),
|
|
span_word_14: 0,
|
|
span_word_14_hex: "0x00000000".to_string(),
|
|
mode_word_15: 0,
|
|
mode_word_15_hex: "0x00000000".to_string(),
|
|
words: Vec::new(),
|
|
hex_words: Vec::new(),
|
|
};
|
|
|
|
let probe = parse_runtime_post_span_probe(&bytes, Some(&trailer))
|
|
.expect("post-span probe should parse");
|
|
|
|
assert_eq!(probe.span_target_offset, 0x60);
|
|
assert_eq!(probe.next_nonzero_offset, Some(0x92));
|
|
assert_eq!(probe.next_aligned_candidate_offset, Some(0x8c));
|
|
assert_eq!(probe.header_candidates.len(), 1);
|
|
assert_eq!(probe.header_candidates[0].dense_word_count, 3);
|
|
assert_eq!(probe.header_candidates[0].grounded_alignments.len(), 2);
|
|
assert_eq!(probe.grounded_progress_hits[0], "0x32dc@0x00000090");
|
|
}
|
|
|
|
#[test]
|
|
fn parses_classic_rehydrate_profile_probe() {
|
|
let mut bytes = vec![0u8; 0x220];
|
|
bytes[0x90..0x94].copy_from_slice(&0x32dc0000u32.to_le_bytes());
|
|
bytes[0x94..0x98].copy_from_slice(&0x37140000u32.to_le_bytes());
|
|
bytes[0x1a0..0x1a4].copy_from_slice(&0x37150000u32.to_le_bytes());
|
|
bytes[0xab..0xb7].copy_from_slice(b"test-map.gmp");
|
|
bytes[0xde..0xe6].copy_from_slice(b"Test Map");
|
|
|
|
let post_span = SmpRuntimePostSpanProbe {
|
|
profile_family: "rt3-classic-save-container-v1".to_string(),
|
|
span_target_offset: 0,
|
|
next_nonzero_offset: Some(0x92),
|
|
next_aligned_candidate_offset: Some(0x8c),
|
|
next_aligned_candidate_words: vec![0, 0x32dc0000, 0x37140000, 0x03000000],
|
|
next_aligned_candidate_hex_words: vec![],
|
|
header_candidates: vec![],
|
|
grounded_progress_hits: vec![
|
|
"0x32dc@0x00000090".to_string(),
|
|
"0x3714@0x00000094".to_string(),
|
|
"0x3715@0x000001a0".to_string(),
|
|
],
|
|
};
|
|
|
|
let probe = parse_classic_rehydrate_profile_probe(&bytes, Some(&post_span))
|
|
.expect("classic rehydrate probe should parse");
|
|
|
|
assert_eq!(probe.packed_profile_offset, 0x98);
|
|
assert_eq!(probe.packed_profile_len, 0x108);
|
|
assert_eq!(probe.ascii_runs[0].preview, "test-map.gmp");
|
|
assert_eq!(probe.packed_profile_block.leading_word_0, 0x00000000);
|
|
assert_eq!(
|
|
probe.packed_profile_block.map_path.as_deref(),
|
|
Some("test-map.gmp")
|
|
);
|
|
assert_eq!(
|
|
probe.packed_profile_block.display_name.as_deref(),
|
|
Some("Test Map")
|
|
);
|
|
assert_eq!(probe.packed_profile_block.profile_byte_0x77, 0x00);
|
|
assert_eq!(probe.packed_profile_block.profile_byte_0x82, 0x00);
|
|
assert_eq!(probe.packed_profile_block.profile_byte_0x97, 0x00);
|
|
assert_eq!(probe.packed_profile_block.profile_byte_0xc5, 0x00);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_rt3_105_packed_profile_probe() {
|
|
let mut bytes = vec![0u8; 0x9000];
|
|
let block = 0x73c0usize;
|
|
bytes[block..block + 4].copy_from_slice(&0x00000003u32.to_le_bytes());
|
|
bytes[block + 0x0c..block + 0x10].copy_from_slice(&0x01000000u32.to_le_bytes());
|
|
bytes[block + 0x10..block + 0x1d].copy_from_slice(b"test-105.gmp\0");
|
|
bytes[block + 0x43..block + 0x4c].copy_from_slice(b"Test 105\0");
|
|
bytes[block + 0x77] = 0x07;
|
|
bytes[block + 0x82] = 0x4d;
|
|
bytes[block + 0x84..block + 0x88].copy_from_slice(&0x65010000u32.to_le_bytes());
|
|
|
|
let header_variant_probe = SmpHeaderVariantProbe {
|
|
variant_family: "rt3-105-common-header-v1".to_string(),
|
|
variant_evidence: vec![],
|
|
is_known_family: true,
|
|
};
|
|
let probe = parse_rt3_105_packed_profile_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&header_variant_probe),
|
|
None,
|
|
)
|
|
.expect("1.05 packed profile probe should parse");
|
|
|
|
assert_eq!(probe.profile_family, "rt3-105-save-analog-block-inferred");
|
|
assert_eq!(probe.packed_profile_offset, 0x73c0);
|
|
assert_eq!(probe.packed_profile_len, 0x108);
|
|
assert_eq!(
|
|
probe.packed_profile_block.map_path.as_deref(),
|
|
Some("test-105.gmp")
|
|
);
|
|
assert_eq!(
|
|
probe.packed_profile_block.display_name.as_deref(),
|
|
Some("Test 105")
|
|
);
|
|
assert_eq!(probe.packed_profile_block.profile_byte_0x77, 0x07);
|
|
assert_eq!(probe.packed_profile_block.profile_byte_0x82, 0x4d);
|
|
assert_eq!(probe.packed_profile_block.profile_byte_0x97, 0x00);
|
|
assert_eq!(probe.packed_profile_block.profile_byte_0xc5, 0x00);
|
|
}
|
|
|
|
#[test]
|
|
fn builds_classic_save_load_summary() {
|
|
let summary = build_save_load_summary(
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-classic-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
None,
|
|
None,
|
|
Some(&SmpClassicRehydrateProfileProbe {
|
|
profile_family: "rt3-classic-save-container-v1".to_string(),
|
|
progress_32dc_offset: 0x76e8,
|
|
progress_3714_offset: 0x76ec,
|
|
progress_3715_offset: 0x77f8,
|
|
packed_profile_offset: 0x76f0,
|
|
packed_profile_len: 0x108,
|
|
packed_profile_len_hex: "0x108".to_string(),
|
|
packed_profile_block: SmpClassicPackedProfileBlock {
|
|
relative_len: 0x108,
|
|
relative_len_hex: "0x108".to_string(),
|
|
leading_word_0: 3,
|
|
leading_word_0_hex: "0x00000003".to_string(),
|
|
trailing_zero_word_count_after_leading_word: 3,
|
|
map_path_offset: 0x13,
|
|
map_path: Some("British Isles.gmp".to_string()),
|
|
display_name_offset: 0x46,
|
|
display_name: Some("British Isles".to_string()),
|
|
profile_byte_0x77: 0,
|
|
profile_byte_0x77_hex: "0x00".to_string(),
|
|
profile_byte_0x82: 0,
|
|
profile_byte_0x82_hex: "0x00".to_string(),
|
|
profile_byte_0x97: 0,
|
|
profile_byte_0x97_hex: "0x00".to_string(),
|
|
profile_byte_0xc5: 0,
|
|
profile_byte_0xc5_hex: "0x00".to_string(),
|
|
stable_nonzero_words: vec![],
|
|
},
|
|
ascii_runs: vec![],
|
|
}),
|
|
None,
|
|
None,
|
|
)
|
|
.expect("classic summary");
|
|
|
|
assert_eq!(summary.mechanism_family, "classic-save-rehydrate-v1");
|
|
assert_eq!(summary.mechanism_confidence, "grounded");
|
|
assert_eq!(summary.map_path.as_deref(), Some("British Isles.gmp"));
|
|
assert_eq!(summary.packed_profile_len, Some(0x108));
|
|
}
|
|
|
|
#[test]
|
|
fn builds_rt3_105_save_load_summary_with_candidate_table() {
|
|
let summary = build_save_load_summary(
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
None,
|
|
Some(&SmpRt3105PostSpanBridgeProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
bridge_family: "rt3-105-save-post-span-bridge-v1".to_string(),
|
|
bridge_evidence: vec![],
|
|
span_target_offset: 0x3678,
|
|
next_candidate_offset: Some(0x4f14),
|
|
next_candidate_delta_from_span_target: Some(0x189c),
|
|
packed_profile_offset: 0x73c0,
|
|
packed_profile_delta_from_span_target: 0x3d48,
|
|
next_candidate_delta_from_packed_profile: Some(-0x24ac),
|
|
selector_high_u16: 0x7110,
|
|
selector_high_hex: "0x7110".to_string(),
|
|
descriptor_high_u16: 0x7801,
|
|
descriptor_high_hex: "0x7801".to_string(),
|
|
next_candidate_high_u16_words: vec![0x6200, 0x0000, 0xfff7, 0x5515],
|
|
next_candidate_high_hex_words: vec![],
|
|
}),
|
|
None,
|
|
Some(&SmpRt3105PackedProfileProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
packed_profile_offset: 0x73c0,
|
|
packed_profile_len: 0x108,
|
|
packed_profile_len_hex: "0x108".to_string(),
|
|
packed_profile_block: SmpRt3105PackedProfileBlock {
|
|
relative_len: 0x108,
|
|
relative_len_hex: "0x108".to_string(),
|
|
leading_word_0: 3,
|
|
leading_word_0_hex: "0x00000003".to_string(),
|
|
trailing_zero_word_count_after_leading_word: 2,
|
|
header_flag_word_3: 1,
|
|
header_flag_word_3_hex: "0x00000001".to_string(),
|
|
map_path_offset: 0x10,
|
|
map_path: Some("Alternate USA.gmp".to_string()),
|
|
display_name_offset: 0x43,
|
|
display_name: Some("Alternate USA".to_string()),
|
|
profile_byte_0x77: 0x07,
|
|
profile_byte_0x77_hex: "0x07".to_string(),
|
|
profile_byte_0x82: 0x4d,
|
|
profile_byte_0x82_hex: "0x4d".to_string(),
|
|
profile_byte_0x97: 0,
|
|
profile_byte_0x97_hex: "0x00".to_string(),
|
|
profile_byte_0xc5: 0,
|
|
profile_byte_0xc5_hex: "0x00".to_string(),
|
|
stable_nonzero_words: vec![],
|
|
},
|
|
ascii_runs: vec![],
|
|
}),
|
|
Some(&SmpRt3105SaveNameTableProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-bridge-secondary-block".to_string(),
|
|
semantic_family: "scenario-named-candidate-availability-table".to_string(),
|
|
semantic_alignment: vec![],
|
|
header_offset: 0x6a70,
|
|
header_word_0: 0,
|
|
header_word_0_hex: "0x00000000".to_string(),
|
|
header_word_1: 0,
|
|
header_word_1_hex: "0x00000000".to_string(),
|
|
header_word_2: 0x332e,
|
|
header_word_2_hex: "0x0000332e".to_string(),
|
|
entry_stride: 0x22,
|
|
entry_stride_hex: "0x22".to_string(),
|
|
header_prefix_word_count: 11,
|
|
observed_entry_capacity: 0x44,
|
|
observed_entry_count: 67,
|
|
zero_trailer_entry_count: 3,
|
|
nonzero_trailer_entry_count: 64,
|
|
distinct_trailer_words: vec![0, 1],
|
|
distinct_trailer_hex_words: vec![
|
|
"0x00000000".to_string(),
|
|
"0x00000001".to_string(),
|
|
],
|
|
zero_trailer_entry_names: vec![
|
|
"Nuclear Power Plant".to_string(),
|
|
"Recycling Plant".to_string(),
|
|
"Uranium Mine".to_string(),
|
|
],
|
|
entries_offset: 0x6ad1,
|
|
entries_end_offset: 0x73b7,
|
|
trailing_footer_hex: "dc3200001437000000".to_string(),
|
|
footer_progress_word_0: 0x32dc,
|
|
footer_progress_word_0_hex: "0x000032dc".to_string(),
|
|
footer_progress_word_1: 0x3714,
|
|
footer_progress_word_1_hex: "0x00003714".to_string(),
|
|
footer_trailing_byte: 0,
|
|
footer_trailing_byte_hex: "0x00".to_string(),
|
|
footer_grounded_alignments: vec![],
|
|
entries: vec![],
|
|
evidence: vec![],
|
|
}),
|
|
)
|
|
.expect("1.05 summary");
|
|
|
|
assert_eq!(summary.mechanism_family, "rt3-105-save-post-span-bridge-v1");
|
|
assert_eq!(summary.mechanism_confidence, "mixed");
|
|
assert_eq!(summary.map_path.as_deref(), Some("Alternate USA.gmp"));
|
|
assert_eq!(
|
|
summary
|
|
.candidate_table
|
|
.as_ref()
|
|
.expect("candidate table")
|
|
.zero_availability_count,
|
|
3
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn loads_classic_save_slice_from_report() {
|
|
let mut report = inspect_smp_bytes(&[]);
|
|
let classic_probe = SmpClassicRehydrateProfileProbe {
|
|
profile_family: "rt3-classic-save-container-v1".to_string(),
|
|
progress_32dc_offset: 0x76e8,
|
|
progress_3714_offset: 0x76ec,
|
|
progress_3715_offset: 0x77f8,
|
|
packed_profile_offset: 0x76f0,
|
|
packed_profile_len: 0x108,
|
|
packed_profile_len_hex: "0x108".to_string(),
|
|
packed_profile_block: SmpClassicPackedProfileBlock {
|
|
relative_len: 0x108,
|
|
relative_len_hex: "0x108".to_string(),
|
|
leading_word_0: 3,
|
|
leading_word_0_hex: "0x00000003".to_string(),
|
|
trailing_zero_word_count_after_leading_word: 3,
|
|
map_path_offset: 0x13,
|
|
map_path: Some("British Isles.gmp".to_string()),
|
|
display_name_offset: 0x46,
|
|
display_name: Some("British Isles".to_string()),
|
|
profile_byte_0x77: 0,
|
|
profile_byte_0x77_hex: "0x00".to_string(),
|
|
profile_byte_0x82: 0,
|
|
profile_byte_0x82_hex: "0x00".to_string(),
|
|
profile_byte_0x97: 0,
|
|
profile_byte_0x97_hex: "0x00".to_string(),
|
|
profile_byte_0xc5: 0,
|
|
profile_byte_0xc5_hex: "0x00".to_string(),
|
|
stable_nonzero_words: vec![],
|
|
},
|
|
ascii_runs: vec![],
|
|
};
|
|
report.classic_rehydrate_profile_probe = Some(classic_probe.clone());
|
|
report.save_load_summary = build_save_load_summary(
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-classic-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
None,
|
|
None,
|
|
Some(&classic_probe),
|
|
None,
|
|
None,
|
|
);
|
|
|
|
let slice = load_save_slice_from_report(&report).expect("classic save slice");
|
|
assert_eq!(slice.mechanism_family, "classic-save-rehydrate-v1");
|
|
assert_eq!(
|
|
slice
|
|
.profile
|
|
.as_ref()
|
|
.and_then(|profile| profile.map_path.as_deref()),
|
|
Some("British Isles.gmp")
|
|
);
|
|
assert!(slice.candidate_availability_table.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn parses_event_runtime_collection_summary_from_synthetic_chunks() {
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
|
|
let header_words = [1u32, 4, 0, 0, 5, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
|
|
bytes.extend_from_slice(&[0x14, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&[0x11, 0x22, 0x33, 0x44]);
|
|
bytes.extend_from_slice(&[0x55, 0x66, 0x77, 0x88]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(summary.packed_state_version, 0x3e9);
|
|
assert_eq!(summary.live_id_bound, 5);
|
|
assert_eq!(summary.live_record_count, 3);
|
|
assert_eq!(summary.live_entry_ids, vec![1, 3, 5]);
|
|
assert_eq!(summary.records_tag_offset, 96);
|
|
assert_eq!(summary.decoded_record_count, 0);
|
|
assert_eq!(summary.records_with_trigger_kind, 0);
|
|
assert_eq!(summary.records_missing_trigger_kind, 3);
|
|
assert_eq!(summary.nondirect_compact_record_count, 0);
|
|
assert!(
|
|
summary
|
|
.add_building_dispatch_strip_record_indexes
|
|
.is_empty()
|
|
);
|
|
assert!(
|
|
summary
|
|
.add_building_dispatch_strip_descriptor_labels
|
|
.is_empty()
|
|
);
|
|
assert_eq!(summary.records.len(), 3);
|
|
assert_eq!(summary.records[0].decode_status, "unsupported_framing");
|
|
}
|
|
|
|
#[test]
|
|
fn parses_event_runtime_collection_summary_from_u32_tag_chunks() {
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&(EVENT_RUNTIME_COLLECTION_METADATA_TAG as u32).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 as u32).to_le_bytes());
|
|
bytes.extend_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
|
|
bytes.extend_from_slice(&(EVENT_RUNTIME_COLLECTION_CLOSE_TAG as u32).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 from u32 tags");
|
|
|
|
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, 98);
|
|
assert_eq!(summary.decoded_record_count, 0);
|
|
assert_eq!(summary.records_with_trigger_kind, 0);
|
|
assert_eq!(summary.records_missing_trigger_kind, 3);
|
|
assert_eq!(summary.nondirect_compact_record_count, 0);
|
|
assert!(
|
|
summary
|
|
.add_building_dispatch_strip_record_indexes
|
|
.is_empty()
|
|
);
|
|
assert!(
|
|
summary
|
|
.add_building_dispatch_strip_descriptor_labels
|
|
.is_empty()
|
|
);
|
|
assert_eq!(summary.records.len(), 3);
|
|
assert_eq!(summary.records[0].decode_status, "unsupported_framing");
|
|
}
|
|
|
|
#[test]
|
|
fn parses_nondirect_event_runtime_collection_summary_from_u32_tag_chunks() {
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&(EVENT_RUNTIME_COLLECTION_METADATA_TAG as u32).to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
|
|
let header_words = [
|
|
0u32, 6, 10, 20, 30, 3, 0, 1, 0, 0, 0, 0, 0, 1, 1, 23, 0, 0, 0,
|
|
];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
|
|
bytes.extend_from_slice(&[0u8; 18]);
|
|
bytes.extend_from_slice(&(EVENT_RUNTIME_COLLECTION_RECORDS_TAG as u32).to_le_bytes());
|
|
bytes.extend_from_slice(&[0xde, 0xad, 0xbe, 0xef]);
|
|
bytes.extend_from_slice(&(EVENT_RUNTIME_COLLECTION_CLOSE_TAG as u32).to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("non-direct event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.source_kind,
|
|
"packed-event-runtime-collection-nondirect"
|
|
);
|
|
assert_eq!(summary.packed_state_version, 0x3e9);
|
|
assert_eq!(summary.live_id_bound, 30);
|
|
assert_eq!(summary.live_record_count, 3);
|
|
assert_eq!(summary.live_entry_ids, vec![1, 2, 3]);
|
|
assert_eq!(summary.records_tag_offset, 102);
|
|
assert_eq!(summary.decoded_record_count, 0);
|
|
assert_eq!(summary.records_with_trigger_kind, 0);
|
|
assert_eq!(summary.records_missing_trigger_kind, 3);
|
|
assert_eq!(summary.nondirect_compact_record_count, 0);
|
|
assert_eq!(summary.nondirect_compact_records_missing_trigger_kind, 0);
|
|
assert_eq!(summary.records.len(), 3);
|
|
assert_eq!(summary.records[0].decode_status, "unsupported_framing");
|
|
}
|
|
|
|
#[test]
|
|
fn parses_nondirect_compact_event_runtime_record_rows() {
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&(EVENT_RUNTIME_COLLECTION_METADATA_TAG as u32).to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
|
|
let header_words = [
|
|
0u32, 6, 10, 20, 30, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 23, 0, 0, 0,
|
|
];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
|
|
bytes.extend_from_slice(&[0u8; 18]);
|
|
bytes.extend_from_slice(&(EVENT_RUNTIME_COLLECTION_RECORDS_TAG as u32).to_le_bytes());
|
|
|
|
bytes.extend_from_slice(&(PACKED_EVENT_REAL_CONDITION_MARKER as u32).to_le_bytes());
|
|
bytes.extend_from_slice(&1u32.to_le_bytes());
|
|
bytes.extend_from_slice(&u32::MAX.to_le_bytes());
|
|
bytes.push(4);
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.push(2);
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&(PACKED_EVENT_REAL_GROUPED_EFFECT_MARKER as u32).to_le_bytes());
|
|
bytes.extend_from_slice(&1u32.to_le_bytes());
|
|
bytes.extend_from_slice(&43u32.to_le_bytes());
|
|
bytes.extend_from_slice(&1u32.to_le_bytes());
|
|
bytes.push(4);
|
|
bytes.extend_from_slice(&u32::MAX.to_le_bytes());
|
|
bytes.extend_from_slice(&u32::MAX.to_le_bytes());
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&[0u8; 12]);
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&(PACKED_EVENT_REAL_RECORD_TERMINATOR_MARKER as u32).to_le_bytes());
|
|
bytes.extend_from_slice(&(EVENT_RUNTIME_COLLECTION_CLOSE_TAG as u32).to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("non-direct event runtime collection summary should parse");
|
|
assert_eq!(summary.records_with_trigger_kind, 0);
|
|
assert_eq!(summary.records_missing_trigger_kind, 1);
|
|
assert_eq!(summary.nondirect_compact_record_count, 1);
|
|
assert_eq!(summary.nondirect_compact_records_missing_trigger_kind, 1);
|
|
assert!(
|
|
summary
|
|
.add_building_dispatch_strip_record_indexes
|
|
.is_empty()
|
|
);
|
|
assert!(
|
|
summary
|
|
.add_building_dispatch_strip_descriptor_labels
|
|
.is_empty()
|
|
);
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains("all compact non-direct rows currently decode row bodies only")
|
|
}));
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains(
|
|
"records with grouped opcodes already in the 0x00431b20 dispatch strip = [0]",
|
|
)
|
|
}));
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains("decoded grouped rows already reach the 0x00431b20 dispatch strip")
|
|
}));
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains("every currently decoded dispatch-strip row")
|
|
&& line.contains("0x4e99/0x4e9a/0x4e9b")
|
|
&& line.contains("0x4e21/0x4e22")
|
|
}));
|
|
assert!(
|
|
summary
|
|
.control_lane_notes
|
|
.iter()
|
|
.any(|line| { line.contains("0x0042db20 allocates linked 0x1e/0x28 row nodes") })
|
|
);
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains("0x004dba23 sits under the event-editor duplication path")
|
|
}));
|
|
assert!(
|
|
summary.control_lane_notes.iter().any(|line| {
|
|
line.contains("0x00430b50 allocates a fresh live runtime-effect row")
|
|
})
|
|
);
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains("0x00443200..0x004436e3")
|
|
&& line.contains("0x005a57cf")
|
|
&& line.contains("New Beginnings")
|
|
&& line.contains("The American")
|
|
}));
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains("0x004323a0")
|
|
&& line.contains("[event+0x81f]")
|
|
&& line.contains("[event+0x7ef]")
|
|
&& line.contains("0x00432ca1..0x00432cb0")
|
|
&& line.contains("0x00438710")
|
|
}));
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains("0x00442c30")
|
|
&& line.contains("0x00444b50")
|
|
&& line.contains("0x0062be18/0x0062bae0")
|
|
&& line.contains("Open Aus")
|
|
&& line.contains("Win - Gold")
|
|
&& line.contains("Win - Silver")
|
|
}));
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains("SP - GOLD")
|
|
&& line.contains("0x00443526")
|
|
&& line.contains("1 to 5")
|
|
&& line.contains("Labor")
|
|
&& line.contains("0x00443601")
|
|
&& line.contains("0 to 2")
|
|
}));
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains("0x00431b20 dispatch-strip opcodes present in decoded grouped rows = [4]")
|
|
}));
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains(
|
|
"decoded grouped descriptor labels present in the 0x00431b20 dispatch strip = [\"Company Variable 1\"]",
|
|
)
|
|
}));
|
|
let record = summary
|
|
.records
|
|
.first()
|
|
.expect("first compact non-direct record");
|
|
|
|
assert_eq!(record.decode_status, "parity_only");
|
|
assert_eq!(record.payload_family, "real_packed_nondirect_compact_v1");
|
|
assert_eq!(record.standalone_condition_row_count, 1);
|
|
assert_eq!(record.standalone_condition_rows.len(), 1);
|
|
assert_eq!(record.standalone_condition_rows[0].raw_condition_id, -1);
|
|
assert_eq!(record.standalone_condition_rows[0].subtype, 4);
|
|
assert_eq!(record.grouped_effect_row_counts, vec![1, 0, 0, 0]);
|
|
assert_eq!(record.grouped_effect_rows.len(), 1);
|
|
assert_eq!(record.grouped_effect_rows[0].descriptor_id, 43);
|
|
assert_eq!(record.grouped_effect_rows[0].raw_scalar_value, 1);
|
|
assert_eq!(record.grouped_effect_rows[0].opcode, 4);
|
|
assert!(record.notes.iter().any(|line| {
|
|
line.contains("compact non-direct 0x4e99/0x4e9a/0x4e9b map-bundle row framing")
|
|
}));
|
|
assert!(
|
|
record
|
|
.notes
|
|
.iter()
|
|
.any(|line| { line.contains("does not materialize the compact control lane") })
|
|
);
|
|
assert!(record.notes.iter().any(|line| {
|
|
line.contains("0x0042e050 reached from editor duplication at 0x004dba23")
|
|
}));
|
|
assert!(
|
|
record.notes.iter().any(|line| {
|
|
line.contains("0x00430b50 allocates a fresh live runtime-effect row")
|
|
})
|
|
);
|
|
assert!(
|
|
record
|
|
.notes
|
|
.iter()
|
|
.any(|line| { line.contains("compact signature family = nondirect-") })
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn summarizes_add_building_dispatch_strip_rows_from_nondirect_compact_records() {
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&(EVENT_RUNTIME_COLLECTION_METADATA_TAG as u32).to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
|
|
let header_words = [
|
|
0u32, 6, 10, 20, 30, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 23, 0, 0, 0,
|
|
];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
|
|
bytes.extend_from_slice(&[0u8; 18]);
|
|
bytes.extend_from_slice(&(EVENT_RUNTIME_COLLECTION_RECORDS_TAG as u32).to_le_bytes());
|
|
bytes.extend_from_slice(&(PACKED_EVENT_REAL_CONDITION_MARKER as u32).to_le_bytes());
|
|
bytes.extend_from_slice(&1u32.to_le_bytes());
|
|
bytes.extend_from_slice(&u32::MAX.to_le_bytes());
|
|
bytes.push(4);
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.push(2);
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&(PACKED_EVENT_REAL_GROUPED_EFFECT_MARKER as u32).to_le_bytes());
|
|
bytes.extend_from_slice(&1u32.to_le_bytes());
|
|
bytes.extend_from_slice(&548u32.to_le_bytes());
|
|
bytes.extend_from_slice(&1u32.to_le_bytes());
|
|
bytes.push(8);
|
|
bytes.extend_from_slice(&u32::MAX.to_le_bytes());
|
|
bytes.extend_from_slice(&u32::MAX.to_le_bytes());
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&[0u8; 12]);
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&0u32.to_le_bytes());
|
|
bytes.extend_from_slice(&(PACKED_EVENT_REAL_RECORD_TERMINATOR_MARKER as u32).to_le_bytes());
|
|
bytes.extend_from_slice(&(EVENT_RUNTIME_COLLECTION_CLOSE_TAG as u32).to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("non-direct event runtime collection summary should parse");
|
|
|
|
assert_eq!(summary.add_building_dispatch_strip_record_indexes, vec![0]);
|
|
assert_eq!(
|
|
summary.add_building_dispatch_strip_row_shape_families,
|
|
vec!["[0:8:1]".to_string()]
|
|
);
|
|
assert_eq!(
|
|
summary.add_building_dispatch_strip_signature_families,
|
|
vec!["nondirect-ge1e-h0001-ffff-0004-0000-0200-p0000-0000-0000-ffff".to_string()]
|
|
);
|
|
assert_eq!(
|
|
summary.add_building_dispatch_strip_condition_tuple_families,
|
|
vec!["[-1:4]".to_string()]
|
|
);
|
|
assert_eq!(
|
|
summary.add_building_dispatch_strip_signature_condition_clusters,
|
|
vec![
|
|
"nondirect-ge1e-h0001-ffff-0004-0000-0200-p0000-0000-0000-ffff :: [-1:4]"
|
|
.to_string()
|
|
]
|
|
);
|
|
assert_eq!(
|
|
summary.add_building_dispatch_strip_descriptor_labels,
|
|
vec!["Add Building Port01".to_string()]
|
|
);
|
|
assert_eq!(
|
|
summary.add_building_dispatch_strip_records_with_trigger_kind,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
summary.add_building_dispatch_strip_records_missing_trigger_kind,
|
|
1
|
|
);
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains(
|
|
"records with Add Building descriptors in the 0x00431b20 dispatch strip = [0]",
|
|
)
|
|
}));
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains(
|
|
"decoded Add Building descriptor labels present in the 0x00431b20 dispatch strip = [\"Add Building Port01\"]",
|
|
)
|
|
}));
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains(
|
|
"Add Building row-shape families present in the 0x00431b20 dispatch strip = [\"[0:8:1]\"]",
|
|
)
|
|
}));
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains(
|
|
"Add Building signature families present in the 0x00431b20 dispatch strip = [\"nondirect-ge1e-h0001-ffff-0004-0000-0200-p0000-0000-0000-ffff\"]",
|
|
)
|
|
}));
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains(
|
|
"Add Building condition-tuple families present in the 0x00431b20 dispatch strip = [\"[-1:4]\"]",
|
|
)
|
|
}));
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains(
|
|
"Add Building signature/condition clusters present in the 0x00431b20 dispatch strip = [\"nondirect-ge1e-h0001-ffff-0004-0000-0200-p0000-0000-0000-ffff :: [-1:4]\"]",
|
|
)
|
|
}));
|
|
assert!(summary.control_lane_notes.iter().any(|line| {
|
|
line.contains("every currently decoded Add Building dispatch-strip row still has null trigger kind")
|
|
}));
|
|
}
|
|
|
|
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_with_trigger_kind, 1);
|
|
assert_eq!(summary.records_missing_trigger_kind, 0);
|
|
assert_eq!(summary.trigger_kinds_present, vec![7]);
|
|
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].trigger_kind, Some(7));
|
|
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_with_trigger_kind, 1);
|
|
assert_eq!(summary.records_missing_trigger_kind, 0);
|
|
assert_eq!(summary.trigger_kinds_present, vec![8]);
|
|
assert_eq!(summary.records[0].decode_status, "executable");
|
|
assert_eq!(summary.records[0].payload_family, "synthetic_harness");
|
|
assert!(summary.records[0].executable_import_ready);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_real_style_event_runtime_record_with_zero_rows() {
|
|
let record_body = build_real_event_record(
|
|
[b"Alpha", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0x63,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 1,
|
|
modifier_flag_0x7f9: 1,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 1, 2, 3],
|
|
grouped_scope_checkboxes_0x7ff: [1, 0, 1, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, 10, -1, 22],
|
|
}),
|
|
&[],
|
|
[&[], &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(summary.decoded_record_count, 1);
|
|
assert_eq!(summary.imported_runtime_record_count, 0);
|
|
assert_eq!(summary.records[0].decode_status, "parity_only");
|
|
assert_eq!(summary.records[0].payload_family, "real_packed_v1");
|
|
assert_eq!(summary.records[0].trigger_kind, Some(7));
|
|
assert_eq!(summary.records[0].one_shot, Some(true));
|
|
assert_eq!(
|
|
summary.records[0]
|
|
.compact_control
|
|
.as_ref()
|
|
.expect("real compact control should parse")
|
|
.primary_selector_0x7f0,
|
|
0x63
|
|
);
|
|
assert_eq!(summary.records[0].text_bands[0].preview, "Alpha");
|
|
assert_eq!(summary.records[0].standalone_condition_row_count, 0);
|
|
assert_eq!(summary.records[0].standalone_condition_rows.len(), 0);
|
|
assert!(summary.records[0].negative_sentinel_scope.is_none());
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_row_counts,
|
|
vec![0, 0, 0, 0]
|
|
);
|
|
assert_eq!(summary.records[0].grouped_effect_rows.len(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_real_style_rows_and_side_strings() {
|
|
let condition_row = build_real_condition_row(-1, 4, 0x30, Some("AutoPlant"));
|
|
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 2,
|
|
opcode: 8,
|
|
raw_scalar_value: 7,
|
|
value_byte_0x09: 1,
|
|
value_dword_0x0d: 12,
|
|
value_byte_0x11: 2,
|
|
value_byte_0x12: 3,
|
|
value_word_0x14: 24,
|
|
value_word_0x16: 36,
|
|
locomotive_name: Some("Mikado"),
|
|
});
|
|
let group0_rows = vec![grouped_row];
|
|
let record_body = build_real_event_record(
|
|
[b"Gamma", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 6,
|
|
primary_selector_0x7f0: 0x2a,
|
|
grouped_mode_0x7f4: 1,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 2,
|
|
modifier_flag_0x7fa: 3,
|
|
grouped_target_scope_ordinals_0x7fb: [1, 4, 7, 8],
|
|
grouped_scope_checkboxes_0x7ff: [0, 1, 0, 1],
|
|
summary_toggle_0x800: 0,
|
|
grouped_territory_selectors_0x80f: [11, -1, 33, -1],
|
|
}),
|
|
&[condition_row],
|
|
[&group0_rows, &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(summary.records[0].standalone_condition_rows.len(), 1);
|
|
assert_eq!(
|
|
summary.records[0]
|
|
.compact_control
|
|
.as_ref()
|
|
.expect("real compact control should parse")
|
|
.grouped_target_scope_ordinals_0x7fb,
|
|
vec![1, 4, 7, 8]
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].standalone_condition_rows[0].raw_condition_id,
|
|
-1
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].standalone_condition_rows[0]
|
|
.candidate_name
|
|
.as_deref(),
|
|
Some("AutoPlant")
|
|
);
|
|
let negative_sentinel_scope = summary.records[0]
|
|
.negative_sentinel_scope
|
|
.as_ref()
|
|
.expect("negative-sentinel scope summary should decode");
|
|
assert_eq!(
|
|
negative_sentinel_scope.company_test_scope,
|
|
RuntimeCompanyConditionTestScope::SelectedCompanyOnly
|
|
);
|
|
assert_eq!(
|
|
negative_sentinel_scope.player_test_scope,
|
|
RuntimePlayerConditionTestScope::AiPlayersOnly
|
|
);
|
|
assert!(!negative_sentinel_scope.territory_scope_selector_is_0x63);
|
|
assert_eq!(negative_sentinel_scope.source_row_indexes, vec![0]);
|
|
assert_eq!(summary.records[0].grouped_effect_rows.len(), 1);
|
|
assert_eq!(summary.records[0].grouped_effect_rows[0].opcode, 8);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.descriptor_label
|
|
.as_deref(),
|
|
Some("Company Cash")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0].target_mask_bits,
|
|
Some(0x01)
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0].row_shape,
|
|
"multivalue_scalar"
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.semantic_family
|
|
.as_deref(),
|
|
Some("multivalue_scalar")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.semantic_preview
|
|
.as_deref(),
|
|
Some("Set Company Cash to 7 with aux [2, 3, 24, 36]")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.locomotive_name
|
|
.as_deref(),
|
|
Some("Mikado")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_actions,
|
|
vec![RuntimeEffect::SetCompanyCash {
|
|
target: RuntimeCompanyTarget::SelectedCompany,
|
|
value: 7,
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_real_special_condition_descriptor_from_checked_in_metadata() {
|
|
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 108,
|
|
opcode: 3,
|
|
raw_scalar_value: 1,
|
|
value_byte_0x09: 0,
|
|
value_dword_0x0d: 0,
|
|
value_byte_0x11: 0,
|
|
value_byte_0x12: 0,
|
|
value_word_0x14: 0,
|
|
value_word_0x16: 0,
|
|
locomotive_name: None,
|
|
});
|
|
let group0_rows = vec![grouped_row];
|
|
let record_body = build_real_event_record(
|
|
[b"World", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[],
|
|
[&group0_rows, &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.descriptor_label
|
|
.as_deref(),
|
|
Some("Use Wartime Cargos")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.parameter_family
|
|
.as_deref(),
|
|
Some("special_condition_scalar")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_actions,
|
|
vec![RuntimeEffect::SetSpecialCondition {
|
|
label: "Use Wartime Cargos".to_string(),
|
|
value: 1,
|
|
}]
|
|
);
|
|
assert!(summary.records[0].executable_import_ready);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_real_candidate_availability_descriptor_from_checked_in_metadata() {
|
|
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 109,
|
|
opcode: 3,
|
|
raw_scalar_value: 1,
|
|
value_byte_0x09: 0,
|
|
value_dword_0x0d: 0,
|
|
value_byte_0x11: 0,
|
|
value_byte_0x12: 0,
|
|
value_word_0x14: 0,
|
|
value_word_0x16: 0,
|
|
locomotive_name: None,
|
|
});
|
|
let group0_rows = vec![grouped_row];
|
|
let record_body = build_real_event_record(
|
|
[b"World", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[],
|
|
[&group0_rows, &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.descriptor_label
|
|
.as_deref(),
|
|
Some("Turbo Diesel Availability")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.parameter_family
|
|
.as_deref(),
|
|
Some("candidate_availability_scalar")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_actions,
|
|
vec![RuntimeEffect::SetCandidateAvailability {
|
|
name: "Turbo Diesel".to_string(),
|
|
value: 1,
|
|
}]
|
|
);
|
|
assert!(summary.records[0].executable_import_ready);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_real_special_condition_threshold_from_checked_in_world_condition_metadata() {
|
|
let condition_row = build_real_condition_row_with_threshold(3835, 0, 1, None);
|
|
let record_body = build_real_event_record(
|
|
[b"World", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[condition_row],
|
|
[&[], &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].standalone_condition_rows[0]
|
|
.metric
|
|
.as_deref(),
|
|
Some("Special Condition: Use Wartime Cargos")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].standalone_condition_rows[0]
|
|
.semantic_family
|
|
.as_deref(),
|
|
Some("world_state_threshold")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_conditions,
|
|
vec![RuntimeCondition::SpecialConditionThreshold {
|
|
label: "Use Wartime Cargos".to_string(),
|
|
comparator: RuntimeConditionComparator::Ge,
|
|
value: 1,
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_real_candidate_availability_threshold_from_checked_in_world_condition_metadata() {
|
|
let condition_row = build_real_condition_row_with_threshold(
|
|
REAL_CANDIDATE_AVAILABILITY_CONDITION_TEMPLATE_ID,
|
|
0,
|
|
2,
|
|
Some("Mogul"),
|
|
);
|
|
let record_body = build_real_event_record(
|
|
[b"World", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[condition_row],
|
|
[&[], &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].standalone_condition_rows[0]
|
|
.metric
|
|
.as_deref(),
|
|
Some("Candidate Availability: Mogul")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_conditions,
|
|
vec![RuntimeCondition::CandidateAvailabilityThreshold {
|
|
name: "Mogul".to_string(),
|
|
comparator: RuntimeConditionComparator::Ge,
|
|
value: 2,
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_real_economic_status_threshold_from_checked_in_world_condition_metadata() {
|
|
let condition_row = build_real_condition_row_with_threshold(2350, 0, 4, None);
|
|
let record_body = build_real_event_record(
|
|
[b"World", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[condition_row],
|
|
[&[], &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].standalone_condition_rows[0]
|
|
.metric
|
|
.as_deref(),
|
|
Some("Economic Status")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_conditions,
|
|
vec![RuntimeCondition::EconomicStatusCodeThreshold {
|
|
comparator: RuntimeConditionComparator::Ge,
|
|
value: 4,
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_real_named_locomotive_availability_threshold_from_checked_in_metadata() {
|
|
let condition_row = build_real_condition_row_with_threshold(
|
|
REAL_NAMED_LOCOMOTIVE_AVAILABILITY_CONDITION_ID,
|
|
4,
|
|
42,
|
|
Some("Big Boy"),
|
|
);
|
|
let record_body = build_real_event_record(
|
|
[b"World", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[condition_row],
|
|
[&[], &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].standalone_condition_rows[0]
|
|
.metric
|
|
.as_deref(),
|
|
Some("Named Locomotive Availability: Big Boy")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].standalone_condition_rows[0]
|
|
.semantic_family
|
|
.as_deref(),
|
|
Some("world_scalar_threshold")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_conditions,
|
|
vec![RuntimeCondition::NamedLocomotiveAvailabilityThreshold {
|
|
name: "Big Boy".to_string(),
|
|
comparator: RuntimeConditionComparator::Eq,
|
|
value: 42,
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_real_named_locomotive_cost_threshold_from_checked_in_metadata() {
|
|
let condition_row = build_real_condition_row_with_threshold(
|
|
REAL_NAMED_LOCOMOTIVE_COST_CONDITION_ID,
|
|
0,
|
|
250000,
|
|
Some("Locomotive 1"),
|
|
);
|
|
let record_body = build_real_event_record(
|
|
[b"World", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[condition_row],
|
|
[&[], &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].standalone_condition_rows[0]
|
|
.metric
|
|
.as_deref(),
|
|
Some("Named Locomotive Cost: Locomotive 1")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_conditions,
|
|
vec![RuntimeCondition::NamedLocomotiveCostThreshold {
|
|
name: "Locomotive 1".to_string(),
|
|
comparator: RuntimeConditionComparator::Ge,
|
|
value: 250000,
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_real_named_cargo_production_threshold_from_checked_in_metadata() {
|
|
let condition_row = build_real_condition_row_with_threshold(
|
|
REAL_CARGO_PRODUCTION_CONDITION_TEMPLATE_ID,
|
|
4,
|
|
125,
|
|
Some("Cargo Production Slot 1"),
|
|
);
|
|
let record_body = build_real_event_record(
|
|
[b"World", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[condition_row],
|
|
[&[], &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].standalone_condition_rows[0]
|
|
.metric
|
|
.as_deref(),
|
|
Some("Cargo Production: Cargo Production Slot 1")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].standalone_condition_rows[0].recovered_cargo_slot,
|
|
Some(1)
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_conditions,
|
|
vec![RuntimeCondition::CargoProductionSlotThreshold {
|
|
slot: 1,
|
|
label: "Cargo Production Slot 1".to_string(),
|
|
comparator: RuntimeConditionComparator::Eq,
|
|
value: 125,
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_real_world_scalar_thresholds_from_checked_in_metadata() {
|
|
let condition_rows = vec![
|
|
build_real_condition_row_with_threshold(
|
|
REAL_CARGO_PRODUCTION_TOTAL_CONDITION_ID,
|
|
0,
|
|
200,
|
|
None,
|
|
),
|
|
build_real_condition_row_with_threshold(
|
|
REAL_FACTORY_PRODUCTION_TOTAL_CONDITION_ID,
|
|
4,
|
|
125,
|
|
None,
|
|
),
|
|
build_real_condition_row_with_threshold(
|
|
REAL_FARM_MINE_PRODUCTION_TOTAL_CONDITION_ID,
|
|
4,
|
|
75,
|
|
None,
|
|
),
|
|
build_real_condition_row_with_threshold(
|
|
REAL_OTHER_CARGO_PRODUCTION_TOTAL_CONDITION_ID,
|
|
4,
|
|
30,
|
|
None,
|
|
),
|
|
build_real_condition_row_with_threshold(
|
|
REAL_LIMITED_TRACK_BUILDING_AMOUNT_CONDITION_ID,
|
|
4,
|
|
18,
|
|
None,
|
|
),
|
|
build_real_condition_row_with_threshold(
|
|
REAL_TERRITORY_ACCESS_COST_CONDITION_ID,
|
|
4,
|
|
750000,
|
|
None,
|
|
),
|
|
];
|
|
let record_body = build_real_event_record(
|
|
[b"World", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&condition_rows,
|
|
[&[], &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].standalone_condition_rows[0]
|
|
.metric
|
|
.as_deref(),
|
|
Some("Cargo Production Total")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].standalone_condition_rows[0]
|
|
.semantic_family
|
|
.as_deref(),
|
|
Some("world_scalar_threshold")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_conditions,
|
|
vec![
|
|
RuntimeCondition::CargoProductionTotalThreshold {
|
|
comparator: RuntimeConditionComparator::Ge,
|
|
value: 200,
|
|
},
|
|
RuntimeCondition::FactoryProductionTotalThreshold {
|
|
comparator: RuntimeConditionComparator::Eq,
|
|
value: 125,
|
|
},
|
|
RuntimeCondition::FarmMineProductionTotalThreshold {
|
|
comparator: RuntimeConditionComparator::Eq,
|
|
value: 75,
|
|
},
|
|
RuntimeCondition::OtherCargoProductionTotalThreshold {
|
|
comparator: RuntimeConditionComparator::Eq,
|
|
value: 30,
|
|
},
|
|
RuntimeCondition::LimitedTrackBuildingAmountThreshold {
|
|
comparator: RuntimeConditionComparator::Eq,
|
|
value: 18,
|
|
},
|
|
RuntimeCondition::TerritoryAccessCostThreshold {
|
|
comparator: RuntimeConditionComparator::Eq,
|
|
value: 750000,
|
|
},
|
|
]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_real_world_flag_condition_from_checked_in_world_condition_metadata() {
|
|
let condition_row = build_real_condition_row_with_threshold(2535, 4, 1, None);
|
|
let record_body = build_real_event_record(
|
|
[b"World", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[condition_row],
|
|
[&[], &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].standalone_condition_rows[0]
|
|
.metric
|
|
.as_deref(),
|
|
Some("World Flag: Disable Stock Buying and Selling")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].standalone_condition_rows[0]
|
|
.semantic_family
|
|
.as_deref(),
|
|
Some("world_flag_equals")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_conditions,
|
|
vec![RuntimeCondition::WorldFlagEquals {
|
|
key: "world.disable_stock_buying_and_selling".to_string(),
|
|
value: true,
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_checked_in_world_scalar_condition_metadata() {
|
|
let named_cargo =
|
|
real_ordinary_condition_metadata(REAL_CARGO_PRODUCTION_CONDITION_TEMPLATE_ID)
|
|
.expect("named cargo condition metadata should exist");
|
|
assert_eq!(named_cargo.label, "%1 Production");
|
|
|
|
let availability =
|
|
real_ordinary_condition_metadata(REAL_NAMED_LOCOMOTIVE_AVAILABILITY_CONDITION_ID)
|
|
.expect("availability condition metadata should exist");
|
|
assert_eq!(availability.label, "Unknown Loco Available");
|
|
|
|
let cost = real_ordinary_condition_metadata(REAL_NAMED_LOCOMOTIVE_COST_CONDITION_ID)
|
|
.expect("cost condition metadata should exist");
|
|
assert_eq!(cost.label, "Unknown Loco Cost");
|
|
|
|
let cargo = real_ordinary_condition_metadata(REAL_CARGO_PRODUCTION_TOTAL_CONDITION_ID)
|
|
.expect("cargo condition metadata should exist");
|
|
assert_eq!(cargo.label, "All Cargo Production");
|
|
|
|
let factory = real_ordinary_condition_metadata(REAL_FACTORY_PRODUCTION_TOTAL_CONDITION_ID)
|
|
.expect("factory production condition metadata should exist");
|
|
assert_eq!(factory.label, "All Factory Production");
|
|
|
|
let farm_mine =
|
|
real_ordinary_condition_metadata(REAL_FARM_MINE_PRODUCTION_TOTAL_CONDITION_ID)
|
|
.expect("farm/mine production condition metadata should exist");
|
|
assert_eq!(farm_mine.label, "All Farm/Mine Production");
|
|
|
|
let build_limit =
|
|
real_ordinary_condition_metadata(REAL_LIMITED_TRACK_BUILDING_AMOUNT_CONDITION_ID)
|
|
.expect("build-limit condition metadata should exist");
|
|
assert_eq!(build_limit.label, "Limited Track Building Amount");
|
|
|
|
let access_cost = real_ordinary_condition_metadata(REAL_TERRITORY_ACCESS_COST_CONDITION_ID)
|
|
.expect("territory-access-cost condition metadata should exist");
|
|
assert_eq!(access_cost.label, "Access Rights Cost:");
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_checked_in_chairman_and_governance_condition_metadata() {
|
|
let world_variable = real_ordinary_condition_metadata(REAL_WORLD_VARIABLE_1_CONDITION_ID)
|
|
.expect("world-variable condition metadata should exist");
|
|
assert_eq!(world_variable.label, "Game Variable 1");
|
|
|
|
let player_variable = real_ordinary_condition_metadata(REAL_PLAYER_VARIABLE_3_CONDITION_ID)
|
|
.expect("player-variable condition metadata should exist");
|
|
assert_eq!(player_variable.label, "Player Variable 3");
|
|
|
|
let chairman_cash = real_ordinary_condition_metadata(REAL_CHAIRMAN_CASH_CONDITION_ID)
|
|
.expect("chairman cash condition metadata should exist");
|
|
assert_eq!(chairman_cash.label, "Player Cash");
|
|
|
|
let holdings = real_ordinary_condition_metadata(REAL_CHAIRMAN_HOLDINGS_TOTAL_CONDITION_ID)
|
|
.expect("chairman holdings condition metadata should exist");
|
|
assert_eq!(holdings.label, "Player Stock Value");
|
|
|
|
let net_worth = real_ordinary_condition_metadata(REAL_CHAIRMAN_NET_WORTH_CONDITION_ID)
|
|
.expect("chairman net worth condition metadata should exist");
|
|
assert_eq!(net_worth.label, "Player Net Worth");
|
|
|
|
let purchasing_power =
|
|
real_ordinary_condition_metadata(REAL_CHAIRMAN_PURCHASING_POWER_CONDITION_ID)
|
|
.expect("chairman purchasing-power condition metadata should exist");
|
|
assert_eq!(purchasing_power.label, "Purchasing Power");
|
|
|
|
let investor_confidence =
|
|
real_ordinary_condition_metadata(REAL_INVESTOR_CONFIDENCE_CONDITION_ID)
|
|
.expect("investor-confidence condition metadata should exist");
|
|
assert_eq!(investor_confidence.label, "Investor Confidence");
|
|
|
|
let credit_rating = real_ordinary_condition_metadata(REAL_CREDIT_RATING_CONDITION_ID)
|
|
.expect("credit-rating condition metadata should exist");
|
|
assert_eq!(credit_rating.label, "Credit Rating");
|
|
|
|
let prime_rate = real_ordinary_condition_metadata(REAL_PRIME_RATE_CONDITION_ID)
|
|
.expect("prime-rate condition metadata should exist");
|
|
assert_eq!(prime_rate.label, "Prime Rate");
|
|
|
|
let management_attitude =
|
|
real_ordinary_condition_metadata(REAL_MANAGEMENT_ATTITUDE_CONDITION_ID)
|
|
.expect("management-attitude condition metadata should exist");
|
|
assert_eq!(management_attitude.label, "Management Attitude");
|
|
|
|
let book_value = real_ordinary_condition_metadata(REAL_BOOK_VALUE_PER_SHARE_CONDITION_ID)
|
|
.expect("book value condition metadata should exist");
|
|
assert_eq!(book_value.label, "Book Value Per Share");
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_world_variable_condition() {
|
|
let row = SmpLoadedPackedEventConditionRowSummary {
|
|
row_index: 0,
|
|
raw_condition_id: REAL_WORLD_VARIABLE_1_CONDITION_ID,
|
|
subtype: 4,
|
|
flag_bytes: {
|
|
let mut bytes = vec![0; 25];
|
|
bytes[0..4].copy_from_slice(&111_i32.to_le_bytes());
|
|
bytes
|
|
},
|
|
candidate_name: None,
|
|
comparator: Some("eq".to_string()),
|
|
metric: Some("Game Variable 1".to_string()),
|
|
semantic_family: Some("numeric_threshold".to_string()),
|
|
semantic_preview: Some("Test Game Variable 1 == 111".to_string()),
|
|
recovered_cargo_slot: None,
|
|
recovered_cargo_class: None,
|
|
requires_candidate_name_binding: false,
|
|
notes: vec![],
|
|
};
|
|
|
|
assert_eq!(
|
|
decode_real_condition_row(&row, None),
|
|
Some(RuntimeCondition::WorldVariableThreshold {
|
|
index: 1,
|
|
comparator: RuntimeConditionComparator::Eq,
|
|
value: 111,
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_player_variable_condition_from_selected_player_scope() {
|
|
let row = SmpLoadedPackedEventConditionRowSummary {
|
|
row_index: 0,
|
|
raw_condition_id: REAL_PLAYER_VARIABLE_3_CONDITION_ID,
|
|
subtype: 4,
|
|
flag_bytes: {
|
|
let mut bytes = vec![0; 25];
|
|
bytes[0..4].copy_from_slice(&333_i32.to_le_bytes());
|
|
bytes
|
|
},
|
|
candidate_name: None,
|
|
comparator: Some("eq".to_string()),
|
|
metric: Some("Player Variable 3".to_string()),
|
|
semantic_family: Some("numeric_threshold".to_string()),
|
|
semantic_preview: Some("Test Player Variable 3 == 333".to_string()),
|
|
recovered_cargo_slot: None,
|
|
recovered_cargo_class: None,
|
|
requires_candidate_name_binding: false,
|
|
notes: vec![],
|
|
};
|
|
let negative_scope = SmpLoadedPackedEventNegativeSentinelScopeSummary {
|
|
company_test_scope: RuntimeCompanyConditionTestScope::Disabled,
|
|
player_test_scope: RuntimePlayerConditionTestScope::SelectedPlayerOnly,
|
|
territory_scope_selector_is_0x63: false,
|
|
source_row_indexes: vec![0],
|
|
};
|
|
|
|
assert_eq!(
|
|
decode_real_condition_row(&row, Some(&negative_scope)),
|
|
Some(RuntimeCondition::PlayerVariableThreshold {
|
|
target: RuntimePlayerTarget::SelectedPlayer,
|
|
index: 3,
|
|
comparator: RuntimeConditionComparator::Eq,
|
|
value: 333,
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_territory_variable_condition_with_world_territory_scope() {
|
|
let row = SmpLoadedPackedEventConditionRowSummary {
|
|
row_index: 0,
|
|
raw_condition_id: REAL_TERRITORY_VARIABLE_4_CONDITION_ID,
|
|
subtype: 4,
|
|
flag_bytes: {
|
|
let mut bytes = vec![0; 25];
|
|
bytes[0..4].copy_from_slice(&444_i32.to_le_bytes());
|
|
bytes
|
|
},
|
|
candidate_name: None,
|
|
comparator: Some("eq".to_string()),
|
|
metric: Some("Territory Variable 4".to_string()),
|
|
semantic_family: Some("numeric_threshold".to_string()),
|
|
semantic_preview: Some("Test Territory Variable 4 == 444".to_string()),
|
|
recovered_cargo_slot: None,
|
|
recovered_cargo_class: None,
|
|
requires_candidate_name_binding: false,
|
|
notes: vec![],
|
|
};
|
|
let negative_scope = SmpLoadedPackedEventNegativeSentinelScopeSummary {
|
|
company_test_scope: RuntimeCompanyConditionTestScope::Disabled,
|
|
player_test_scope: RuntimePlayerConditionTestScope::Disabled,
|
|
territory_scope_selector_is_0x63: true,
|
|
source_row_indexes: vec![0],
|
|
};
|
|
|
|
assert_eq!(
|
|
decode_real_condition_row(&row, Some(&negative_scope)),
|
|
Some(RuntimeCondition::TerritoryVariableThreshold {
|
|
target: RuntimeTerritoryTarget::AllTerritories,
|
|
index: 4,
|
|
comparator: RuntimeConditionComparator::Eq,
|
|
value: 444,
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_chairman_cash_condition_from_selected_player_scope() {
|
|
let row = SmpLoadedPackedEventConditionRowSummary {
|
|
row_index: 0,
|
|
raw_condition_id: REAL_CHAIRMAN_CASH_CONDITION_ID,
|
|
subtype: 4,
|
|
flag_bytes: {
|
|
let mut bytes = vec![0; 25];
|
|
bytes[0..4].copy_from_slice(&500_i32.to_le_bytes());
|
|
bytes
|
|
},
|
|
candidate_name: None,
|
|
comparator: Some("eq".to_string()),
|
|
metric: Some("Player Cash".to_string()),
|
|
semantic_family: Some("numeric_threshold".to_string()),
|
|
semantic_preview: Some("Test Player Cash == 500".to_string()),
|
|
recovered_cargo_slot: None,
|
|
recovered_cargo_class: None,
|
|
requires_candidate_name_binding: false,
|
|
notes: vec![],
|
|
};
|
|
let negative_scope = SmpLoadedPackedEventNegativeSentinelScopeSummary {
|
|
company_test_scope: RuntimeCompanyConditionTestScope::Disabled,
|
|
player_test_scope: RuntimePlayerConditionTestScope::SelectedPlayerOnly,
|
|
territory_scope_selector_is_0x63: false,
|
|
source_row_indexes: vec![0],
|
|
};
|
|
|
|
assert_eq!(
|
|
decode_real_condition_row(&row, Some(&negative_scope)),
|
|
Some(RuntimeCondition::ChairmanNumericThreshold {
|
|
target: RuntimeChairmanTarget::SelectedChairman,
|
|
metric: RuntimeChairmanMetric::CurrentCash,
|
|
comparator: RuntimeConditionComparator::Eq,
|
|
value: 500,
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_book_value_per_share_condition_to_company_metric() {
|
|
let row = SmpLoadedPackedEventConditionRowSummary {
|
|
row_index: 0,
|
|
raw_condition_id: REAL_BOOK_VALUE_PER_SHARE_CONDITION_ID,
|
|
subtype: 4,
|
|
flag_bytes: {
|
|
let mut bytes = vec![0; 25];
|
|
bytes[0..4].copy_from_slice(&2620_i32.to_le_bytes());
|
|
bytes
|
|
},
|
|
candidate_name: None,
|
|
comparator: Some("eq".to_string()),
|
|
metric: Some("Book Value Per Share".to_string()),
|
|
semantic_family: Some("numeric_threshold".to_string()),
|
|
semantic_preview: Some("Test Book Value Per Share == 2620".to_string()),
|
|
recovered_cargo_slot: None,
|
|
recovered_cargo_class: None,
|
|
requires_candidate_name_binding: false,
|
|
notes: vec![],
|
|
};
|
|
|
|
assert_eq!(
|
|
decode_real_condition_row(&row, None),
|
|
Some(RuntimeCondition::CompanyNumericThreshold {
|
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
|
metric: RuntimeCompanyMetric::BookValuePerShare,
|
|
comparator: RuntimeConditionComparator::Eq,
|
|
value: 2620,
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_investor_confidence_condition_to_company_metric() {
|
|
let row = SmpLoadedPackedEventConditionRowSummary {
|
|
row_index: 0,
|
|
raw_condition_id: REAL_INVESTOR_CONFIDENCE_CONDITION_ID,
|
|
subtype: 4,
|
|
flag_bytes: {
|
|
let mut bytes = vec![0; 25];
|
|
bytes[0..4].copy_from_slice(&37_i32.to_le_bytes());
|
|
bytes
|
|
},
|
|
candidate_name: None,
|
|
comparator: Some("eq".to_string()),
|
|
metric: Some("Investor Confidence".to_string()),
|
|
semantic_family: Some("numeric_threshold".to_string()),
|
|
semantic_preview: Some("Test Investor Confidence == 37".to_string()),
|
|
recovered_cargo_slot: None,
|
|
recovered_cargo_class: None,
|
|
requires_candidate_name_binding: false,
|
|
notes: vec![],
|
|
};
|
|
|
|
assert_eq!(
|
|
decode_real_condition_row(&row, None),
|
|
Some(RuntimeCondition::CompanyNumericThreshold {
|
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
|
metric: RuntimeCompanyMetric::InvestorConfidence,
|
|
comparator: RuntimeConditionComparator::Eq,
|
|
value: 37,
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_management_attitude_condition_to_company_metric() {
|
|
let row = SmpLoadedPackedEventConditionRowSummary {
|
|
row_index: 0,
|
|
raw_condition_id: REAL_MANAGEMENT_ATTITUDE_CONDITION_ID,
|
|
subtype: 4,
|
|
flag_bytes: {
|
|
let mut bytes = vec![0; 25];
|
|
bytes[0..4].copy_from_slice(&58_i32.to_le_bytes());
|
|
bytes
|
|
},
|
|
candidate_name: None,
|
|
comparator: Some("eq".to_string()),
|
|
metric: Some("Management Attitude".to_string()),
|
|
semantic_family: Some("numeric_threshold".to_string()),
|
|
semantic_preview: Some("Test Management Attitude == 58".to_string()),
|
|
recovered_cargo_slot: None,
|
|
recovered_cargo_class: None,
|
|
requires_candidate_name_binding: false,
|
|
notes: vec![],
|
|
};
|
|
|
|
assert_eq!(
|
|
decode_real_condition_row(&row, None),
|
|
Some(RuntimeCondition::CompanyNumericThreshold {
|
|
target: RuntimeCompanyTarget::ConditionTrueCompany,
|
|
metric: RuntimeCompanyMetric::ManagementAttitude,
|
|
comparator: RuntimeConditionComparator::Eq,
|
|
value: 58,
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_checked_in_world_flag_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(110).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Disable Stock Buying and Selling");
|
|
assert_eq!(metadata.parameter_family, "world_flag_toggle");
|
|
assert_eq!(
|
|
metadata.runtime_key,
|
|
Some("world.disable_stock_buying_and_selling")
|
|
);
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_checked_in_credit_rating_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(56).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Credit Rating");
|
|
assert_eq!(metadata.target_mask_bits, 0x0b);
|
|
assert_eq!(metadata.parameter_family, "company_governance_scalar");
|
|
assert_eq!(
|
|
real_grouped_effect_runtime_status_name(metadata.runtime_status),
|
|
"executable"
|
|
);
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn checked_in_event_effect_table_covers_the_full_exported_descriptor_set() {
|
|
let rows = checked_in_event_effect_descriptor_rows();
|
|
assert_eq!(rows.len(), 614);
|
|
for descriptor_id in 0..614_u32 {
|
|
assert!(
|
|
real_grouped_effect_descriptor_metadata(descriptor_id).is_some(),
|
|
"descriptor {descriptor_id} should be recoverable from the checked-in effect table"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_checked_in_prime_rate_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(57).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Prime Rate");
|
|
assert_eq!(metadata.target_mask_bits, 0x0b);
|
|
assert_eq!(metadata.parameter_family, "company_governance_scalar");
|
|
assert_eq!(
|
|
real_grouped_effect_runtime_status_name(metadata.runtime_status),
|
|
"executable"
|
|
);
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn classifies_shell_owned_finance_descriptors_from_checked_in_effect_table() {
|
|
let stock_prices =
|
|
real_grouped_effect_descriptor_metadata(55).expect("descriptor metadata should exist");
|
|
assert_eq!(stock_prices.label, "Stock Prices");
|
|
assert_eq!(
|
|
stock_prices.parameter_family,
|
|
"company_finance_shell_scalar"
|
|
);
|
|
assert_eq!(
|
|
real_grouped_effect_runtime_status_name(stock_prices.runtime_status),
|
|
"shell_owned"
|
|
);
|
|
assert!(!stock_prices.executable_in_runtime);
|
|
|
|
let merger_premium =
|
|
real_grouped_effect_descriptor_metadata(58).expect("descriptor metadata should exist");
|
|
assert_eq!(merger_premium.label, "Merger Premium");
|
|
assert_eq!(
|
|
merger_premium.parameter_family,
|
|
"company_finance_shell_scalar"
|
|
);
|
|
assert_eq!(
|
|
real_grouped_effect_runtime_status_name(merger_premium.runtime_status),
|
|
"shell_owned"
|
|
);
|
|
assert!(!merger_premium.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_credit_rating_descriptor_into_company_governance_scalar_effect() {
|
|
let row_bytes = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 56,
|
|
raw_scalar_value: 640,
|
|
opcode: 3,
|
|
value_byte_0x09: 0,
|
|
value_dword_0x0d: 0,
|
|
value_byte_0x11: 0,
|
|
value_byte_0x12: 0,
|
|
value_word_0x14: 0,
|
|
value_word_0x16: 0,
|
|
locomotive_name: None,
|
|
});
|
|
let record_body = build_real_event_record(
|
|
[b"Gov", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [1, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[],
|
|
[&[row_bytes], &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.descriptor_label
|
|
.as_deref(),
|
|
Some("Credit Rating")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.grouped_target_subject
|
|
.as_deref(),
|
|
Some("company")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_actions,
|
|
vec![RuntimeEffect::SetCompanyGovernanceScalar {
|
|
target: RuntimeCompanyTarget::SelectedCompany,
|
|
metric: RuntimeCompanyMetric::CreditRating,
|
|
value: 640,
|
|
}]
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_recovered_world_toggle_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(111).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Disable Margin Buying/Short Selling Stock");
|
|
assert_eq!(metadata.parameter_family, "world_flag_toggle");
|
|
assert_eq!(
|
|
runtime_world_flag_key(metadata),
|
|
Some("world.disable_margin_buying_short_selling_stock".to_string())
|
|
);
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_limited_track_building_amount_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(122).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Limited Track Building Amount");
|
|
assert_eq!(metadata.parameter_family, "world_track_build_limit_scalar");
|
|
assert_eq!(metadata.runtime_key, None);
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_recovered_late_world_toggle_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(143).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Disable Train Crashes AND Breakdowns");
|
|
assert_eq!(metadata.parameter_family, "world_flag_toggle");
|
|
assert_eq!(
|
|
runtime_world_flag_key(metadata),
|
|
Some("world.disable_train_crashes_and_breakdowns".to_string())
|
|
);
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_recovered_locomotive_availability_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(250).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Big Boy 4-8-8-4 Availability");
|
|
assert_eq!(metadata.target_mask_bits, 0x08);
|
|
assert_eq!(metadata.parameter_family, "locomotive_availability_scalar");
|
|
assert_eq!(metadata.runtime_key, None);
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_upper_band_recovered_locomotive_availability_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(457).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Upper-Band Locomotive Availability Slot 1");
|
|
assert_eq!(metadata.target_mask_bits, 0x08);
|
|
assert_eq!(metadata.parameter_family, "locomotive_availability_scalar");
|
|
assert_eq!(recovered_locomotive_availability_loco_id(457), None);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_extended_lower_band_locomotive_availability_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(301).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Zephyr Availability");
|
|
assert_eq!(metadata.parameter_family, "locomotive_availability_scalar");
|
|
assert_eq!(recovered_locomotive_availability_loco_id(301), Some(61));
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_recovered_locomotive_availability_row_with_structured_locomotive_id() {
|
|
let row_bytes = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 250,
|
|
raw_scalar_value: 1,
|
|
opcode: 3,
|
|
value_byte_0x09: 0,
|
|
value_dword_0x0d: 0,
|
|
value_byte_0x11: 0,
|
|
value_byte_0x12: 0,
|
|
value_word_0x14: 0,
|
|
value_word_0x16: 0,
|
|
locomotive_name: None,
|
|
});
|
|
|
|
let row = parse_real_grouped_effect_row_summary(&row_bytes, 0, 0, None)
|
|
.expect("row should parse");
|
|
|
|
assert_eq!(row.descriptor_id, 250);
|
|
assert_eq!(
|
|
row.descriptor_label.as_deref(),
|
|
Some("Big Boy 4-8-8-4 Availability")
|
|
);
|
|
assert_eq!(row.recovered_locomotive_id, Some(10));
|
|
assert_eq!(
|
|
row.parameter_family.as_deref(),
|
|
Some("locomotive_availability_scalar")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_recovered_cargo_production_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(230).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Cargo Production Slot 1");
|
|
assert_eq!(metadata.target_mask_bits, 0x08);
|
|
assert_eq!(metadata.parameter_family, "cargo_production_scalar");
|
|
assert_eq!(metadata.runtime_key, None);
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_recovered_all_cargo_price_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(105).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "All Cargo Prices");
|
|
assert_eq!(metadata.target_mask_bits, 0x08);
|
|
assert_eq!(metadata.parameter_family, "cargo_price_scalar");
|
|
assert_eq!(metadata.runtime_key, None);
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_named_cargo_price_slot_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(106).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Alcohol Price");
|
|
assert_eq!(metadata.target_mask_bits, 0x08);
|
|
assert_eq!(metadata.parameter_family, "cargo_price_scalar");
|
|
assert_eq!(metadata.runtime_key, None);
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_runtime_variable_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(43).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Company Variable 1");
|
|
assert_eq!(metadata.target_mask_bits, 0x01);
|
|
assert_eq!(metadata.parameter_family, "runtime_variable_scalar");
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_recovered_aggregate_cargo_production_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(177).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "All Cargo Production");
|
|
assert_eq!(metadata.target_mask_bits, 0x08);
|
|
assert_eq!(metadata.parameter_family, "cargo_production_scalar");
|
|
assert_eq!(metadata.runtime_key, None);
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_grounded_named_cargo_production_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(180).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Alcohol Production");
|
|
assert_eq!(metadata.target_mask_bits, 0x08);
|
|
assert_eq!(metadata.parameter_family, "cargo_production_scalar");
|
|
assert_eq!(metadata.runtime_key, None);
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_recovered_lower_band_locomotive_cost_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(352).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "2-D-2 Cost");
|
|
assert_eq!(metadata.target_mask_bits, 0x08);
|
|
assert_eq!(metadata.parameter_family, "locomotive_cost_scalar");
|
|
assert_eq!(metadata.runtime_key, None);
|
|
assert_eq!(recovered_locomotive_cost_loco_id(352), Some(1));
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_recovered_upper_band_locomotive_cost_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(475).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Upper-Band Locomotive Cost Slot 1");
|
|
assert_eq!(metadata.parameter_family, "locomotive_cost_scalar");
|
|
assert_eq!(recovered_locomotive_cost_loco_id(475), None);
|
|
assert!(!metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_extended_lower_band_locomotive_cost_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(412).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Zephyr Cost");
|
|
assert_eq!(metadata.parameter_family, "locomotive_cost_scalar");
|
|
assert_eq!(recovered_locomotive_cost_loco_id(412), Some(61));
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_recovered_territory_access_cost_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(453).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Territory Access Cost");
|
|
assert_eq!(metadata.target_mask_bits, 0x08);
|
|
assert_eq!(metadata.parameter_family, "territory_access_cost_scalar");
|
|
assert_eq!(metadata.runtime_key, None);
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_recovered_locomotive_cost_row_with_structured_locomotive_id() {
|
|
let row_bytes = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 352,
|
|
raw_scalar_value: 250000,
|
|
opcode: 3,
|
|
value_byte_0x09: 0,
|
|
value_dword_0x0d: 0,
|
|
value_byte_0x11: 0,
|
|
value_byte_0x12: 0,
|
|
value_word_0x14: 0,
|
|
value_word_0x16: 0,
|
|
locomotive_name: None,
|
|
});
|
|
|
|
let row = parse_real_grouped_effect_row_summary(&row_bytes, 0, 0, None)
|
|
.expect("row should parse");
|
|
|
|
assert_eq!(row.descriptor_id, 352);
|
|
assert_eq!(row.descriptor_label.as_deref(), Some("2-D-2 Cost"));
|
|
assert_eq!(row.recovered_locomotive_id, Some(1));
|
|
assert_eq!(
|
|
row.parameter_family.as_deref(),
|
|
Some("locomotive_cost_scalar")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_grounded_named_cargo_production_row_with_label() {
|
|
let row_bytes = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 180,
|
|
raw_scalar_value: 160,
|
|
opcode: 3,
|
|
value_byte_0x09: 0,
|
|
value_dword_0x0d: 0,
|
|
value_byte_0x11: 0,
|
|
value_byte_0x12: 0,
|
|
value_word_0x14: 0,
|
|
value_word_0x16: 0,
|
|
locomotive_name: None,
|
|
});
|
|
|
|
let row = parse_real_grouped_effect_row_summary(&row_bytes, 0, 0, None)
|
|
.expect("row should parse");
|
|
|
|
assert_eq!(row.descriptor_id, 180);
|
|
assert_eq!(row.descriptor_label.as_deref(), Some("Alcohol Production"));
|
|
assert_eq!(row.recovered_cargo_label.as_deref(), Some("Alcohol"));
|
|
assert_eq!(
|
|
row.parameter_family.as_deref(),
|
|
Some("cargo_production_scalar")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_grounded_named_cargo_price_row_with_label() {
|
|
let row_bytes = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 106,
|
|
raw_scalar_value: 140,
|
|
opcode: 3,
|
|
value_byte_0x09: 0,
|
|
value_dword_0x0d: 0,
|
|
value_byte_0x11: 0,
|
|
value_byte_0x12: 0,
|
|
value_word_0x14: 0,
|
|
value_word_0x16: 0,
|
|
locomotive_name: None,
|
|
});
|
|
|
|
let row = parse_real_grouped_effect_row_summary(&row_bytes, 0, 0, None)
|
|
.expect("row should parse");
|
|
|
|
assert_eq!(row.descriptor_id, 106);
|
|
assert_eq!(row.descriptor_label.as_deref(), Some("Alcohol Price"));
|
|
assert_eq!(row.recovered_cargo_label.as_deref(), Some("Alcohol"));
|
|
assert_eq!(row.parameter_family.as_deref(), Some("cargo_price_scalar"));
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_recovered_locomotive_policy_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(454).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "All Steam Locos Avail.");
|
|
assert_eq!(metadata.parameter_family, "world_flag_toggle");
|
|
assert_eq!(
|
|
metadata.runtime_key,
|
|
Some("world.all_steam_locos_available")
|
|
);
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_recovered_locomotive_policy_descriptor_from_checked_in_metadata() {
|
|
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 454,
|
|
opcode: 0,
|
|
raw_scalar_value: 1,
|
|
value_byte_0x09: 0,
|
|
value_dword_0x0d: 0,
|
|
value_byte_0x11: 0,
|
|
value_byte_0x12: 0,
|
|
value_word_0x14: 0,
|
|
value_word_0x16: 0,
|
|
locomotive_name: None,
|
|
});
|
|
let group0_rows = vec![grouped_row];
|
|
let record_body = build_real_event_record(
|
|
[b"Locos", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 6,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[],
|
|
[&group0_rows, &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.descriptor_label
|
|
.as_deref(),
|
|
Some("All Steam Locos Avail.")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.parameter_family
|
|
.as_deref(),
|
|
Some("world_flag_toggle")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_actions,
|
|
vec![RuntimeEffect::SetWorldFlag {
|
|
key: "world.all_steam_locos_available".to_string(),
|
|
value: true,
|
|
}]
|
|
);
|
|
assert!(summary.records[0].executable_import_ready);
|
|
}
|
|
|
|
#[test]
|
|
fn looks_up_checked_in_deactivate_player_descriptor_metadata() {
|
|
let metadata =
|
|
real_grouped_effect_descriptor_metadata(14).expect("descriptor metadata should exist");
|
|
|
|
assert_eq!(metadata.label, "Deactivate Player");
|
|
assert_eq!(metadata.parameter_family, "player_lifecycle_toggle");
|
|
assert_eq!(metadata.runtime_key, None);
|
|
assert!(metadata.executable_in_runtime);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_real_deactivate_player_descriptor_from_checked_in_metadata() {
|
|
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 14,
|
|
opcode: 1,
|
|
raw_scalar_value: 1,
|
|
value_byte_0x09: 0,
|
|
value_dword_0x0d: 0,
|
|
value_byte_0x11: 0,
|
|
value_byte_0x12: 0,
|
|
value_word_0x14: 0,
|
|
value_word_0x16: 0,
|
|
locomotive_name: None,
|
|
});
|
|
let group0_rows = vec![grouped_row];
|
|
let record_body = build_real_event_record(
|
|
[b"Players", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [1, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[],
|
|
[&group0_rows, &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.descriptor_label
|
|
.as_deref(),
|
|
Some("Deactivate Player")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.parameter_family
|
|
.as_deref(),
|
|
Some("player_lifecycle_toggle")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_actions,
|
|
vec![RuntimeEffect::DeactivatePlayer {
|
|
target: RuntimePlayerTarget::SelectedPlayer,
|
|
}]
|
|
);
|
|
assert!(summary.records[0].executable_import_ready);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_real_world_flag_descriptor_with_checked_in_runtime_key() {
|
|
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 110,
|
|
opcode: 0,
|
|
raw_scalar_value: 1,
|
|
value_byte_0x09: 0,
|
|
value_dword_0x0d: 0,
|
|
value_byte_0x11: 0,
|
|
value_byte_0x12: 0,
|
|
value_word_0x14: 0,
|
|
value_word_0x16: 0,
|
|
locomotive_name: None,
|
|
});
|
|
let group0_rows = vec![grouped_row];
|
|
let record_body = build_real_event_record(
|
|
[b"World", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[],
|
|
[&group0_rows, &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.descriptor_label
|
|
.as_deref(),
|
|
Some("Disable Stock Buying and Selling")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.parameter_family
|
|
.as_deref(),
|
|
Some("world_flag_toggle")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_actions,
|
|
vec![RuntimeEffect::SetWorldFlag {
|
|
key: "world.disable_stock_buying_and_selling".to_string(),
|
|
value: true,
|
|
}]
|
|
);
|
|
assert!(summary.records[0].executable_import_ready);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_recovered_world_toggle_descriptor_family() {
|
|
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 131,
|
|
opcode: 0,
|
|
raw_scalar_value: 1,
|
|
value_byte_0x09: 0,
|
|
value_dword_0x0d: 0,
|
|
value_byte_0x11: 0,
|
|
value_byte_0x12: 0,
|
|
value_word_0x14: 0,
|
|
value_word_0x16: 0,
|
|
locomotive_name: None,
|
|
});
|
|
let group0_rows = vec![grouped_row];
|
|
let record_body = build_real_event_record(
|
|
[b"World", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[],
|
|
[&group0_rows, &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.descriptor_label
|
|
.as_deref(),
|
|
Some("Disable Starting Any Companies")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.parameter_family
|
|
.as_deref(),
|
|
Some("world_flag_toggle")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_actions,
|
|
vec![RuntimeEffect::SetWorldFlag {
|
|
key: "world.disable_starting_any_companies".to_string(),
|
|
value: true,
|
|
}]
|
|
);
|
|
assert!(summary.records[0].executable_import_ready);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_limited_track_building_amount_descriptor_family() {
|
|
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 122,
|
|
opcode: 3,
|
|
raw_scalar_value: 18,
|
|
value_byte_0x09: 0,
|
|
value_dword_0x0d: 0,
|
|
value_byte_0x11: 0,
|
|
value_byte_0x12: 0,
|
|
value_word_0x14: 0,
|
|
value_word_0x16: 0,
|
|
locomotive_name: None,
|
|
});
|
|
let group0_rows = vec![grouped_row];
|
|
let record_body = build_real_event_record(
|
|
[b"World", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 6,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[],
|
|
[&group0_rows, &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.descriptor_label
|
|
.as_deref(),
|
|
Some("Limited Track Building Amount")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.parameter_family
|
|
.as_deref(),
|
|
Some("world_track_build_limit_scalar")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_actions,
|
|
vec![RuntimeEffect::SetLimitedTrackBuildingAmount { value: 18 }]
|
|
);
|
|
assert!(summary.records[0].executable_import_ready);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_recovered_late_world_toggle_descriptor_family() {
|
|
let grouped_row = build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 144,
|
|
opcode: 0,
|
|
raw_scalar_value: 1,
|
|
value_byte_0x09: 0,
|
|
value_dword_0x0d: 0,
|
|
value_byte_0x11: 0,
|
|
value_byte_0x12: 0,
|
|
value_word_0x14: 0,
|
|
value_word_0x16: 0,
|
|
locomotive_name: None,
|
|
});
|
|
let group0_rows = vec![grouped_row];
|
|
let record_body = build_real_event_record(
|
|
[b"World", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [1, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[],
|
|
[&group0_rows, &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.descriptor_label
|
|
.as_deref(),
|
|
Some("AI Ignore Territories At Startup")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.parameter_family
|
|
.as_deref(),
|
|
Some("world_flag_toggle")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].decoded_actions,
|
|
vec![RuntimeEffect::SetWorldFlag {
|
|
key: "world.ai_ignore_territories_at_startup".to_string(),
|
|
value: true,
|
|
}]
|
|
);
|
|
assert!(summary.records[0].executable_import_ready);
|
|
}
|
|
|
|
#[test]
|
|
fn decodes_negative_sentinel_scope_modifiers_and_territory_marker() {
|
|
for (value, expected) in [
|
|
(0, RuntimeCompanyConditionTestScope::Disabled),
|
|
(1, RuntimeCompanyConditionTestScope::AllCompanies),
|
|
(2, RuntimeCompanyConditionTestScope::SelectedCompanyOnly),
|
|
(3, RuntimeCompanyConditionTestScope::AiCompaniesOnly),
|
|
(4, RuntimeCompanyConditionTestScope::HumanCompaniesOnly),
|
|
] {
|
|
assert_eq!(decode_company_condition_test_scope(value), Some(expected));
|
|
}
|
|
for (value, expected) in [
|
|
(0, RuntimePlayerConditionTestScope::Disabled),
|
|
(1, RuntimePlayerConditionTestScope::AllPlayers),
|
|
(2, RuntimePlayerConditionTestScope::SelectedPlayerOnly),
|
|
(3, RuntimePlayerConditionTestScope::AiPlayersOnly),
|
|
(4, RuntimePlayerConditionTestScope::HumanPlayersOnly),
|
|
] {
|
|
assert_eq!(decode_player_condition_test_scope(value), Some(expected));
|
|
}
|
|
|
|
let rows = vec![SmpLoadedPackedEventConditionRowSummary {
|
|
row_index: 0,
|
|
raw_condition_id: -1,
|
|
subtype: 4,
|
|
flag_bytes: vec![0x30; 25],
|
|
candidate_name: Some("AutoPlant".to_string()),
|
|
comparator: None,
|
|
metric: None,
|
|
semantic_family: None,
|
|
semantic_preview: None,
|
|
recovered_cargo_slot: None,
|
|
recovered_cargo_class: None,
|
|
requires_candidate_name_binding: false,
|
|
notes: vec![],
|
|
}];
|
|
let summary = derive_negative_sentinel_scope_summary(
|
|
&rows,
|
|
&SmpLoadedPackedEventCompactControlSummary {
|
|
mode_byte_0x7ef: 6,
|
|
primary_selector_0x7f0: 0x63,
|
|
grouped_mode_0x7f4: 2,
|
|
one_shot_header_0x7f5: 1,
|
|
modifier_flag_0x7f9: 4,
|
|
modifier_flag_0x7fa: 2,
|
|
grouped_target_scope_ordinals_0x7fb: vec![0, 1, 2, 3],
|
|
grouped_scope_checkboxes_0x7ff: vec![1, 0, 0, 0],
|
|
summary_toggle_0x800: 1,
|
|
grouped_territory_selectors_0x80f: vec![-1, -1, -1, -1],
|
|
},
|
|
)
|
|
.expect("negative sentinel summary should derive");
|
|
assert_eq!(
|
|
summary.company_test_scope,
|
|
RuntimeCompanyConditionTestScope::HumanCompaniesOnly
|
|
);
|
|
assert_eq!(
|
|
summary.player_test_scope,
|
|
RuntimePlayerConditionTestScope::SelectedPlayerOnly
|
|
);
|
|
assert!(summary.territory_scope_selector_is_0x63);
|
|
assert_eq!(summary.source_row_indexes, vec![0]);
|
|
}
|
|
|
|
#[test]
|
|
fn classifies_real_grouped_row_semantic_families() {
|
|
let grouped_rows = vec![
|
|
build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 2,
|
|
opcode: 1,
|
|
raw_scalar_value: 1,
|
|
value_byte_0x09: 0,
|
|
value_dword_0x0d: 0,
|
|
value_byte_0x11: 0,
|
|
value_byte_0x12: 0,
|
|
value_word_0x14: 0,
|
|
value_word_0x16: 0,
|
|
locomotive_name: None,
|
|
}),
|
|
build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 2,
|
|
opcode: 4,
|
|
raw_scalar_value: 25,
|
|
value_byte_0x09: 0,
|
|
value_dword_0x0d: 0,
|
|
value_byte_0x11: 0,
|
|
value_byte_0x12: 0,
|
|
value_word_0x14: 2,
|
|
value_word_0x16: 6,
|
|
locomotive_name: None,
|
|
}),
|
|
build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 2,
|
|
opcode: 3,
|
|
raw_scalar_value: 250,
|
|
value_byte_0x09: 0,
|
|
value_dword_0x0d: 0,
|
|
value_byte_0x11: 0,
|
|
value_byte_0x12: 0,
|
|
value_word_0x14: 0,
|
|
value_word_0x16: 0,
|
|
locomotive_name: None,
|
|
}),
|
|
build_real_grouped_effect_row(RealGroupedEffectRowSpec {
|
|
descriptor_id: 2,
|
|
opcode: 8,
|
|
raw_scalar_value: 7,
|
|
value_byte_0x09: 1,
|
|
value_dword_0x0d: 12,
|
|
value_byte_0x11: 2,
|
|
value_byte_0x12: 3,
|
|
value_word_0x14: 24,
|
|
value_word_0x16: 36,
|
|
locomotive_name: Some("Mikado"),
|
|
}),
|
|
];
|
|
let record_body = build_real_event_record(
|
|
[b"Semantic", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 7,
|
|
primary_selector_0x7f0: 0x63,
|
|
grouped_mode_0x7f4: 1,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [1, 1, 1, 1],
|
|
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
|
|
summary_toggle_0x800: 0,
|
|
grouped_territory_selectors_0x80f: [-1, -1, -1, -1],
|
|
}),
|
|
&[],
|
|
[&grouped_rows, &[], &[], &[]],
|
|
);
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
let families = summary.records[0]
|
|
.grouped_effect_rows
|
|
.iter()
|
|
.map(|row| row.semantic_family.as_deref().unwrap_or(""))
|
|
.collect::<Vec<_>>();
|
|
assert_eq!(
|
|
families,
|
|
vec![
|
|
"bool_toggle",
|
|
"timed_duration",
|
|
"scalar_assignment",
|
|
"multivalue_scalar",
|
|
]
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[0]
|
|
.semantic_preview
|
|
.as_deref(),
|
|
Some("Set Company Cash to TRUE")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[1]
|
|
.semantic_preview
|
|
.as_deref(),
|
|
Some("Set Company Cash to 25 for 2 years 6 months")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[2]
|
|
.semantic_preview
|
|
.as_deref(),
|
|
Some("Set Company Cash to 250")
|
|
);
|
|
assert_eq!(
|
|
summary.records[0].grouped_effect_rows[3]
|
|
.semantic_preview
|
|
.as_deref(),
|
|
Some("Set Company Cash to 7 with aux [2, 3, 24, 36]")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_truncated_real_style_event_runtime_record() {
|
|
let mut record_body = build_real_event_record(
|
|
[b"Oops", b"", b"", b"", b"", b""],
|
|
Some(RealCompactControlSpec {
|
|
mode_byte_0x7ef: 5,
|
|
primary_selector_0x7f0: 0,
|
|
grouped_mode_0x7f4: 0,
|
|
one_shot_header_0x7f5: 0,
|
|
modifier_flag_0x7f9: 0,
|
|
modifier_flag_0x7fa: 0,
|
|
grouped_target_scope_ordinals_0x7fb: [0, 0, 0, 0],
|
|
grouped_scope_checkboxes_0x7ff: [0, 0, 0, 0],
|
|
summary_toggle_0x800: 0,
|
|
grouped_territory_selectors_0x80f: [0, 0, 0, 0],
|
|
}),
|
|
&[],
|
|
[&[], &[], &[], &[]],
|
|
);
|
|
record_body.pop();
|
|
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_METADATA_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION.to_le_bytes());
|
|
let header_words = [1u32, 4, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
for word in header_words {
|
|
bytes.extend_from_slice(&word.to_le_bytes());
|
|
}
|
|
bytes.extend_from_slice(&[0x00, 0x00]);
|
|
bytes.extend_from_slice(&[0xaa, 0xbb, 0xcc, 0xdd]);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_RECORDS_TAG.to_le_bytes());
|
|
bytes.extend_from_slice(&record_body);
|
|
bytes.extend_from_slice(&EVENT_RUNTIME_COLLECTION_CLOSE_TAG.to_le_bytes());
|
|
|
|
let report = inspect_smp_bytes(&bytes);
|
|
let summary = report
|
|
.event_runtime_collection_summary
|
|
.as_ref()
|
|
.expect("event runtime collection summary should parse");
|
|
|
|
assert_eq!(summary.records[0].decode_status, "unsupported_framing");
|
|
assert_eq!(summary.records[0].payload_family, "unsupported_framing");
|
|
}
|
|
|
|
#[test]
|
|
fn loads_event_runtime_collection_summary_from_report() {
|
|
let mut report = inspect_smp_bytes(&[]);
|
|
let classic_probe = SmpClassicRehydrateProfileProbe {
|
|
profile_family: "rt3-classic-save-container-v1".to_string(),
|
|
progress_32dc_offset: 0x76e8,
|
|
progress_3714_offset: 0x76ec,
|
|
progress_3715_offset: 0x77f8,
|
|
packed_profile_offset: 0x76f0,
|
|
packed_profile_len: 0x108,
|
|
packed_profile_len_hex: "0x108".to_string(),
|
|
packed_profile_block: SmpClassicPackedProfileBlock {
|
|
relative_len: 0x108,
|
|
relative_len_hex: "0x108".to_string(),
|
|
leading_word_0: 3,
|
|
leading_word_0_hex: "0x00000003".to_string(),
|
|
trailing_zero_word_count_after_leading_word: 3,
|
|
map_path_offset: 0x13,
|
|
map_path: Some("British Isles.gmp".to_string()),
|
|
display_name_offset: 0x46,
|
|
display_name: Some("British Isles".to_string()),
|
|
profile_byte_0x77: 0,
|
|
profile_byte_0x77_hex: "0x00".to_string(),
|
|
profile_byte_0x82: 0,
|
|
profile_byte_0x82_hex: "0x00".to_string(),
|
|
profile_byte_0x97: 0,
|
|
profile_byte_0x97_hex: "0x00".to_string(),
|
|
profile_byte_0xc5: 0,
|
|
profile_byte_0xc5_hex: "0x00".to_string(),
|
|
stable_nonzero_words: vec![],
|
|
},
|
|
ascii_runs: vec![],
|
|
};
|
|
report.classic_rehydrate_profile_probe = Some(classic_probe.clone());
|
|
report.save_load_summary = build_save_load_summary(
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-classic-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
None,
|
|
None,
|
|
Some(&classic_probe),
|
|
None,
|
|
None,
|
|
);
|
|
report.event_runtime_collection_summary = Some(SmpLoadedEventRuntimeCollectionSummary {
|
|
source_kind: "packed-event-runtime-collection".to_string(),
|
|
mechanism_family: "classic-save-rehydrate-v1".to_string(),
|
|
mechanism_confidence: "grounded".to_string(),
|
|
container_profile_family: Some("rt3-classic-save-container-v1".to_string()),
|
|
metadata_tag_offset: 0x7100,
|
|
records_tag_offset: 0x7200,
|
|
close_tag_offset: 0x7600,
|
|
packed_state_version: 0x3e9,
|
|
packed_state_version_hex: "0x000003e9".to_string(),
|
|
live_id_bound: 5,
|
|
live_record_count: 3,
|
|
live_entry_ids: vec![1, 3, 5],
|
|
decoded_record_count: 0,
|
|
imported_runtime_record_count: 0,
|
|
records_with_trigger_kind: 0,
|
|
records_missing_trigger_kind: 3,
|
|
nondirect_compact_record_count: 0,
|
|
nondirect_compact_records_missing_trigger_kind: 0,
|
|
trigger_kinds_present: vec![],
|
|
add_building_dispatch_strip_record_indexes: vec![],
|
|
add_building_dispatch_strip_descriptor_labels: vec![],
|
|
add_building_dispatch_strip_records_with_trigger_kind: 0,
|
|
add_building_dispatch_strip_records_missing_trigger_kind: 0,
|
|
add_building_dispatch_strip_row_shape_families: vec![],
|
|
add_building_dispatch_strip_signature_families: vec![],
|
|
add_building_dispatch_strip_condition_tuple_families: vec![],
|
|
add_building_dispatch_strip_signature_condition_clusters: vec![],
|
|
control_lane_notes: vec![],
|
|
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_placed_structure_collection_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.save_region_record_triplet_probe = Some(SmpSaveRegionRecordTripletProbe {
|
|
profile_family: "rt3-classic-save-container-v1".to_string(),
|
|
source_kind: "save-region-record-triplets".to_string(),
|
|
semantic_family: "scenario-save-region-record-triplets".to_string(),
|
|
records_tag_offset: 0x3400,
|
|
close_tag_offset: 0x3500,
|
|
record_count: 2,
|
|
entries: vec![
|
|
SmpSaveRegionRecordTripletEntryProbe {
|
|
record_index: 0,
|
|
name: "Marker09".to_string(),
|
|
record_payload_relative_offset: 0,
|
|
record_payload_relative_offset_hex: "0x0".to_string(),
|
|
name_tag_relative_offset: 0,
|
|
policy_tag_relative_offset: 0x10,
|
|
profile_tag_relative_offset: 0x2e,
|
|
pre_name_prefix_len: 0,
|
|
pre_name_prefix_hex_bytes: Vec::new(),
|
|
pre_name_prefix_dword_candidates: Vec::new(),
|
|
policy_chunk_len: 0x1a,
|
|
profile_chunk_len: 0x40,
|
|
policy_leading_f32_0: 368.0,
|
|
policy_leading_f32_1: 0.0,
|
|
policy_leading_f32_2: 92.0,
|
|
policy_reserved_dwords: vec![0, 0, 0],
|
|
policy_reserved_dword_candidates: Vec::new(),
|
|
policy_trailing_word: 1,
|
|
policy_trailing_word_hex: "0x0001".to_string(),
|
|
profile_collection: Some(SmpSaveRegionProfileCollectionProbe {
|
|
direct_collection_flag: 1,
|
|
entry_stride: 0x22,
|
|
live_id_bound: 18,
|
|
live_record_count: 17,
|
|
entry_start_relative_offset: 0x4d,
|
|
trailing_padding_len: 2,
|
|
entries: vec![
|
|
SmpSaveRegionProfileEntryProbe {
|
|
entry_index: 0,
|
|
row_relative_offset: 0x4d,
|
|
name: "House".to_string(),
|
|
trailing_weight_f32: 0.2,
|
|
},
|
|
SmpSaveRegionProfileEntryProbe {
|
|
entry_index: 1,
|
|
row_relative_offset: 0x6f,
|
|
name: "Farm Corn".to_string(),
|
|
trailing_weight_f32: 0.2,
|
|
},
|
|
],
|
|
}),
|
|
},
|
|
SmpSaveRegionRecordTripletEntryProbe {
|
|
record_index: 1,
|
|
name: "Marker10".to_string(),
|
|
record_payload_relative_offset: 0x6e,
|
|
record_payload_relative_offset_hex: "0x6e".to_string(),
|
|
name_tag_relative_offset: 0x76,
|
|
policy_tag_relative_offset: 0x86,
|
|
profile_tag_relative_offset: 0xa4,
|
|
pre_name_prefix_len: 8,
|
|
pre_name_prefix_hex_bytes: vec![
|
|
"0xaa".to_string(),
|
|
"0xbb".to_string(),
|
|
"0xcc".to_string(),
|
|
"0xdd".to_string(),
|
|
],
|
|
pre_name_prefix_dword_candidates: Vec::new(),
|
|
policy_chunk_len: 0x1a,
|
|
profile_chunk_len: 0x20,
|
|
policy_leading_f32_0: 552.0,
|
|
policy_leading_f32_1: 0.0,
|
|
policy_leading_f32_2: 276.0,
|
|
policy_reserved_dwords: vec![0, 4, 0],
|
|
policy_reserved_dword_candidates: Vec::new(),
|
|
policy_trailing_word: 1,
|
|
policy_trailing_word_hex: "0x0001".to_string(),
|
|
profile_collection: Some(SmpSaveRegionProfileCollectionProbe {
|
|
direct_collection_flag: 1,
|
|
entry_stride: 0x22,
|
|
live_id_bound: 26,
|
|
live_record_count: 24,
|
|
entry_start_relative_offset: 0x50,
|
|
trailing_padding_len: 0,
|
|
entries: vec![SmpSaveRegionProfileEntryProbe {
|
|
entry_index: 0,
|
|
row_relative_offset: 0x50,
|
|
name: "Farm Corn".to_string(),
|
|
trailing_weight_f32: 0.2,
|
|
}],
|
|
}),
|
|
},
|
|
],
|
|
evidence: vec![],
|
|
});
|
|
report.save_region_fixed_row_run_candidate_probe =
|
|
Some(SmpSaveRegionFixedRowRunCandidateProbe {
|
|
profile_family: "rt3-classic-save-container-v1".to_string(),
|
|
source_kind: "save-region-fixed-row-run-candidates".to_string(),
|
|
semantic_family: "scenario-save-region-fixed-row-run-candidates".to_string(),
|
|
target_row_count: 2,
|
|
target_row_stride: 0xbc,
|
|
target_row_stride_hex: "0xbc".to_string(),
|
|
scan_start_offset: 0x5200,
|
|
scan_start_offset_hex: "0x5200".to_string(),
|
|
scan_end_offset: 0x5600,
|
|
scan_end_offset_hex: "0x5600".to_string(),
|
|
candidates: vec![SmpSaveRegionFixedRowRunCandidate {
|
|
count_offset: 0x5300,
|
|
count_offset_hex: "0x5300".to_string(),
|
|
row_count: 2,
|
|
row_stride: 0xbc,
|
|
row_stride_hex: "0xbc".to_string(),
|
|
rows_offset: 0x5310,
|
|
rows_offset_hex: "0x5310".to_string(),
|
|
rows_end_offset: 0x5488,
|
|
rows_end_offset_hex: "0x5488".to_string(),
|
|
distance_to_region_metadata_tag: 0x110,
|
|
distance_to_region_metadata_tag_hex: "0x110".to_string(),
|
|
dword_lane_summaries: vec![],
|
|
shape_signature: "dword0:f32,dword1:zero".to_string(),
|
|
shape_family_signature: "family-a".to_string(),
|
|
trailing_byte_zero_count: 2,
|
|
trailing_byte_nonzero_count: 0,
|
|
trailing_byte_distinct_value_count: 1,
|
|
trailing_byte_sample_values_hex: vec!["0x00".to_string()],
|
|
best_probable_density_lane_relative_offset_hex: Some("0x24".to_string()),
|
|
}],
|
|
evidence: vec![],
|
|
});
|
|
report.save_placed_structure_record_triplet_probe =
|
|
Some(SmpSavePlacedStructureRecordTripletProbe {
|
|
profile_family: "rt3-classic-save-container-v1".to_string(),
|
|
source_kind: "save-placed-structure-record-triplets".to_string(),
|
|
semantic_family: "scenario-save-placed-structure-record-triplets".to_string(),
|
|
records_tag_offset: 0x3600,
|
|
close_tag_offset: 0x3800,
|
|
record_count: 2,
|
|
entries: vec![
|
|
SmpSavePlacedStructureRecordTripletEntryProbe {
|
|
record_index: 0,
|
|
primary_name: "FarmCorn".to_string(),
|
|
secondary_name: "FarmSet".to_string(),
|
|
name_tag_relative_offset: 0,
|
|
policy_tag_relative_offset: 0x10,
|
|
profile_tag_relative_offset: 0x2e,
|
|
policy_chunk_len: 0x1a,
|
|
profile_chunk_len: 0x10,
|
|
policy_f32_lane_0: 1.0,
|
|
policy_f32_lane_1: 2.0,
|
|
policy_f32_lane_2: 3.0,
|
|
policy_f32_lane_3: 4.0,
|
|
policy_f32_lane_4: 5.0,
|
|
policy_reserved_dword: 0,
|
|
policy_trailing_word: 1,
|
|
policy_trailing_word_hex: "0x0001".to_string(),
|
|
profile_open_marker: 0x00005dc1,
|
|
profile_open_marker_hex: "0x00005dc1".to_string(),
|
|
profile_repeated_primary_name: "FarmCorn".to_string(),
|
|
profile_repeated_secondary_name: "FarmSet".to_string(),
|
|
profile_footer_relative_offset: 0x08,
|
|
profile_footer_relative_offset_hex: "0x8".to_string(),
|
|
profile_pre_footer_padding_len: 1,
|
|
profile_pre_footer_padding_hex_bytes: vec!["0x00".to_string()],
|
|
profile_companion_byte_u8: Some(0),
|
|
profile_companion_byte_hex: Some("0x00".to_string()),
|
|
profile_payload_dword: 0,
|
|
profile_payload_dword_hex: "0x00000000".to_string(),
|
|
profile_sentinel_i32: 4,
|
|
profile_status_kind: "farm_growth_stage_bucket".to_string(),
|
|
farm_growth_stage_index: Some(4),
|
|
profile_close_marker: 0x00005dc2,
|
|
profile_close_marker_hex: "0x00005dc2".to_string(),
|
|
},
|
|
SmpSavePlacedStructureRecordTripletEntryProbe {
|
|
record_index: 1,
|
|
primary_name: "StationA".to_string(),
|
|
secondary_name: "StationSetA".to_string(),
|
|
name_tag_relative_offset: 0x40,
|
|
policy_tag_relative_offset: 0x50,
|
|
profile_tag_relative_offset: 0x6e,
|
|
policy_chunk_len: 0x1a,
|
|
profile_chunk_len: 0x10,
|
|
policy_f32_lane_0: 0.0,
|
|
policy_f32_lane_1: 0.0,
|
|
policy_f32_lane_2: 0.0,
|
|
policy_f32_lane_3: 0.0,
|
|
policy_f32_lane_4: 0.0,
|
|
policy_reserved_dword: 0,
|
|
policy_trailing_word: 1,
|
|
policy_trailing_word_hex: "0x0001".to_string(),
|
|
profile_open_marker: 0x00005dc1,
|
|
profile_open_marker_hex: "0x00005dc1".to_string(),
|
|
profile_repeated_primary_name: "StationA".to_string(),
|
|
profile_repeated_secondary_name: "StationSetA".to_string(),
|
|
profile_footer_relative_offset: 0x08,
|
|
profile_footer_relative_offset_hex: "0x8".to_string(),
|
|
profile_pre_footer_padding_len: 1,
|
|
profile_pre_footer_padding_hex_bytes: vec!["0x07".to_string()],
|
|
profile_companion_byte_u8: Some(7),
|
|
profile_companion_byte_hex: Some("0x07".to_string()),
|
|
profile_payload_dword: 0x00005dc1,
|
|
profile_payload_dword_hex: "0x00005dc1".to_string(),
|
|
profile_sentinel_i32: 0,
|
|
profile_status_kind: "opaque_nondefault".to_string(),
|
|
farm_growth_stage_index: None,
|
|
profile_close_marker: 0x00005dc2,
|
|
profile_close_marker_hex: "0x00005dc2".to_string(),
|
|
},
|
|
],
|
|
evidence: vec![],
|
|
});
|
|
report.save_placed_structure_dynamic_side_buffer_probe =
|
|
Some(SmpSavePlacedStructureDynamicSideBufferProbe {
|
|
profile_family: "rt3-classic-save-container-v1".to_string(),
|
|
source_kind: "save-placed-structure-dynamic-side-buffer-records".to_string(),
|
|
semantic_family: "scenario-save-placed-structure-dynamic-side-buffer-records"
|
|
.to_string(),
|
|
metadata_tag_offset: 0x3800,
|
|
records_tag_offset: 0x3900,
|
|
close_tag_offset: 0x3d00,
|
|
records_span_len: 0x400,
|
|
direct_record_stride: 6,
|
|
direct_record_stride_hex: "0x6".to_string(),
|
|
live_id_bound: 0x80,
|
|
live_id_bound_hex: "0x00000080".to_string(),
|
|
live_record_count: 118,
|
|
live_record_count_hex: "0x00000076".to_string(),
|
|
owner_shared_dword: 0xff0000ff,
|
|
owner_shared_dword_hex: "0xff0000ff".to_string(),
|
|
owner_shared_dword_relative_offset: 0,
|
|
owner_shared_dword_matches_first_compact_prefix_leading_dword: true,
|
|
first_record_child_count_after_owner_shared: Some(1),
|
|
first_record_child_count_after_owner_shared_hex: Some("0x0001".to_string()),
|
|
first_record_saved_primary_child_byte_after_owner_shared: Some(0xff),
|
|
first_record_saved_primary_child_byte_after_owner_shared_hex: Some(
|
|
"0xff".to_string(),
|
|
),
|
|
first_record_first_name_tag_relative_offset_after_owner_shared: Some(3),
|
|
prefix_leading_dword: 0xff0000ff,
|
|
prefix_leading_dword_hex: "0xff0000ff".to_string(),
|
|
prefix_trailing_word: 1,
|
|
prefix_trailing_word_hex: "0x0001".to_string(),
|
|
prefix_separator_byte: 0xff,
|
|
prefix_separator_byte_hex: "0xff".to_string(),
|
|
first_embedded_name_tag_relative_offset: 3,
|
|
embedded_name_tag_count: 118,
|
|
decoded_embedded_name_row_count: 118,
|
|
decoded_embedded_name_row_with_tertiary_name_count: 0,
|
|
unique_compact_prefix_pattern_count: 4,
|
|
prefix_leading_dword_matching_embedded_profile_tag_count: 0,
|
|
unique_embedded_name_pair_count: 9,
|
|
first_embedded_primary_name: Some("StationA".to_string()),
|
|
first_embedded_secondary_name: Some("StationSetA".to_string()),
|
|
first_embedded_tertiary_name: None,
|
|
embedded_name_row_samples: vec![],
|
|
compact_prefix_pattern_summaries: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferPrefixPatternSummary {
|
|
prefix_leading_dword: 0xff0000ff,
|
|
prefix_leading_dword_hex: "0xff0000ff".to_string(),
|
|
prefix_trailing_word: 1,
|
|
prefix_trailing_word_hex: "0x0001".to_string(),
|
|
prefix_separator_byte: 0xff,
|
|
prefix_separator_byte_hex: "0xff".to_string(),
|
|
count: 62,
|
|
first_name_tag_relative_offset: 3,
|
|
prefix_leading_dword_matches_embedded_profile_tag: false,
|
|
section_like_primary_name_count: 12,
|
|
cap_like_primary_name_count: 21,
|
|
other_primary_name_count: 29,
|
|
first_primary_name: Some("StationA".to_string()),
|
|
first_secondary_name: Some("StationSetA".to_string()),
|
|
},
|
|
],
|
|
name_pair_summaries: vec![SmpSavePlacedStructureDynamicSideBufferNamePairSummary {
|
|
primary_name: "StationA".to_string(),
|
|
secondary_name: "StationSetA".to_string(),
|
|
count: 14,
|
|
first_name_tag_relative_offset: 3,
|
|
unique_compact_prefix_pattern_count: 1,
|
|
dominant_prefix_leading_dword: 0xff0000ff,
|
|
dominant_prefix_leading_dword_hex: "0xff0000ff".to_string(),
|
|
dominant_prefix_trailing_word: 1,
|
|
dominant_prefix_trailing_word_hex: "0x0001".to_string(),
|
|
dominant_prefix_separator_byte: 0xff,
|
|
dominant_prefix_separator_byte_hex: "0xff".to_string(),
|
|
dominant_prefix_count: 14,
|
|
}],
|
|
payload_envelope_summary: None,
|
|
live_entry_prelude_summary: None,
|
|
evidence: vec![],
|
|
});
|
|
let slice = load_save_slice_from_report(&report).expect("classic save slice");
|
|
let region_collection = slice
|
|
.region_collection
|
|
.expect("region collection should project");
|
|
assert_eq!(region_collection.source_kind, "save-region-record-triplets");
|
|
assert_eq!(region_collection.observed_entry_count, 2);
|
|
assert_eq!(region_collection.entries[0].name, "Marker09");
|
|
assert_eq!(region_collection.entries[1].pre_name_prefix_len, 8);
|
|
assert_eq!(
|
|
region_collection.entries[1].policy_reserved_dwords,
|
|
vec![0, 4, 0]
|
|
);
|
|
assert_eq!(
|
|
region_collection.entries[0]
|
|
.profile_collection
|
|
.as_ref()
|
|
.map(|collection| collection.entries.len()),
|
|
Some(2)
|
|
);
|
|
let region_fixed_row_run_summary = slice
|
|
.region_fixed_row_run_summary
|
|
.expect("region fixed-row summary should project");
|
|
assert_eq!(
|
|
region_fixed_row_run_summary.source_kind,
|
|
"save-region-fixed-row-run-candidates"
|
|
);
|
|
assert_eq!(region_fixed_row_run_summary.candidates.len(), 1);
|
|
assert_eq!(
|
|
region_fixed_row_run_summary.candidates[0].rows_offset_hex,
|
|
"0x5310"
|
|
);
|
|
let collection = slice
|
|
.placed_structure_collection
|
|
.expect("placed structure collection should project");
|
|
assert_eq!(
|
|
collection.source_kind,
|
|
"save-placed-structure-record-triplets"
|
|
);
|
|
assert_eq!(collection.observed_entry_count, 2);
|
|
assert_eq!(collection.entries[0].primary_name, "FarmCorn");
|
|
assert_eq!(collection.entries[0].farm_growth_stage_index, Some(4));
|
|
assert_eq!(collection.entries[1].profile_companion_byte_u8, Some(7));
|
|
let side_buffer_summary = slice
|
|
.placed_structure_dynamic_side_buffer_summary
|
|
.expect("side-buffer summary should project");
|
|
assert_eq!(
|
|
side_buffer_summary.source_kind,
|
|
"save-placed-structure-dynamic-side-buffer-records"
|
|
);
|
|
assert_eq!(side_buffer_summary.observed_entry_count, 118);
|
|
assert_eq!(side_buffer_summary.unique_embedded_name_pair_count, 9);
|
|
assert_eq!(side_buffer_summary.triplet_alignment_overlap_count, 1);
|
|
assert_eq!(
|
|
side_buffer_summary.triplet_alignment_side_buffer_only_name_pair_count,
|
|
0
|
|
);
|
|
assert!(slice.notes.iter().any(|line| {
|
|
line.contains("loaded region triplet rows as first-class context")
|
|
&& line.contains("3 embedded profile rows")
|
|
}));
|
|
assert!(slice.notes.iter().any(|line| {
|
|
line.contains("region fixed-row run summary") && line.contains("Some(\"0x5310\")")
|
|
}));
|
|
assert!(slice.notes.iter().any(|line| {
|
|
line.contains("placed-structure triplet rows as first-class context")
|
|
&& line.contains("2")
|
|
}));
|
|
assert!(slice.notes.iter().any(|line| {
|
|
line.contains("placed-structure dynamic side-buffer summary")
|
|
&& line.contains("118 decoded name rows")
|
|
}));
|
|
}
|
|
|
|
#[test]
|
|
fn loads_rt3_105_save_slice_from_report() {
|
|
let mut report = inspect_smp_bytes(&[]);
|
|
let packed_profile = SmpRt3105PackedProfileProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
packed_profile_offset: 0x73c0,
|
|
packed_profile_len: 0x108,
|
|
packed_profile_len_hex: "0x108".to_string(),
|
|
packed_profile_block: SmpRt3105PackedProfileBlock {
|
|
relative_len: 0x108,
|
|
relative_len_hex: "0x108".to_string(),
|
|
leading_word_0: 3,
|
|
leading_word_0_hex: "0x00000003".to_string(),
|
|
trailing_zero_word_count_after_leading_word: 2,
|
|
header_flag_word_3: 1,
|
|
header_flag_word_3_hex: "0x00000001".to_string(),
|
|
map_path_offset: 0x10,
|
|
map_path: Some("Alternate USA.gmp".to_string()),
|
|
display_name_offset: 0x43,
|
|
display_name: Some("Alternate USA".to_string()),
|
|
profile_byte_0x77: 0x07,
|
|
profile_byte_0x77_hex: "0x07".to_string(),
|
|
profile_byte_0x82: 0x4d,
|
|
profile_byte_0x82_hex: "0x4d".to_string(),
|
|
profile_byte_0x97: 0,
|
|
profile_byte_0x97_hex: "0x00".to_string(),
|
|
profile_byte_0xc5: 0,
|
|
profile_byte_0xc5_hex: "0x00".to_string(),
|
|
stable_nonzero_words: vec![],
|
|
},
|
|
ascii_runs: vec![],
|
|
};
|
|
let name_table = SmpRt3105SaveNameTableProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-bridge-secondary-block".to_string(),
|
|
semantic_family: "scenario-named-candidate-availability-table".to_string(),
|
|
semantic_alignment: vec![],
|
|
header_offset: 0x6a70,
|
|
header_word_0: 0,
|
|
header_word_0_hex: "0x00000000".to_string(),
|
|
header_word_1: 0,
|
|
header_word_1_hex: "0x00000000".to_string(),
|
|
header_word_2: 0x332e,
|
|
header_word_2_hex: "0x0000332e".to_string(),
|
|
entry_stride: 0x22,
|
|
entry_stride_hex: "0x22".to_string(),
|
|
header_prefix_word_count: 11,
|
|
observed_entry_capacity: 0x44,
|
|
observed_entry_count: 2,
|
|
zero_trailer_entry_count: 1,
|
|
nonzero_trailer_entry_count: 1,
|
|
distinct_trailer_words: vec![0, 1],
|
|
distinct_trailer_hex_words: vec!["0x00000000".to_string(), "0x00000001".to_string()],
|
|
zero_trailer_entry_names: vec!["Uranium Mine".to_string()],
|
|
entries_offset: 0x6ad1,
|
|
entries_end_offset: 0x6b15,
|
|
trailing_footer_hex: "dc3200001437000000".to_string(),
|
|
footer_progress_word_0: 0x32dc,
|
|
footer_progress_word_0_hex: "0x000032dc".to_string(),
|
|
footer_progress_word_1: 0x3714,
|
|
footer_progress_word_1_hex: "0x00003714".to_string(),
|
|
footer_trailing_byte: 0,
|
|
footer_trailing_byte_hex: "0x00".to_string(),
|
|
footer_grounded_alignments: vec![],
|
|
entries: vec![
|
|
SmpRt3105SaveNameTableEntry {
|
|
index: 0,
|
|
offset: 0x6ad1,
|
|
text: "AutoPlant".to_string(),
|
|
availability_dword: 1,
|
|
availability_dword_hex: "0x00000001".to_string(),
|
|
trailer_word: 1,
|
|
trailer_word_hex: "0x00000001".to_string(),
|
|
},
|
|
SmpRt3105SaveNameTableEntry {
|
|
index: 1,
|
|
offset: 0x6af3,
|
|
text: "Uranium Mine".to_string(),
|
|
availability_dword: 0,
|
|
availability_dword_hex: "0x00000000".to_string(),
|
|
trailer_word: 0,
|
|
trailer_word_hex: "0x00000000".to_string(),
|
|
},
|
|
],
|
|
evidence: vec![],
|
|
};
|
|
let named_locomotive_table = SmpRt3105SaveNamedLocomotiveAvailabilityProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-direct-locomotive-row-run".to_string(),
|
|
semantic_family: "scenario-named-locomotive-availability-table".to_string(),
|
|
semantic_alignment: vec![],
|
|
entries_offset: 0x7c78,
|
|
entry_stride: RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE,
|
|
entry_stride_hex: format!("0x{:x}", RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE),
|
|
observed_entry_count: 2,
|
|
zero_availability_count: 1,
|
|
zero_availability_names: vec!["Big Boy".to_string()],
|
|
entries_end_offset: 0x7c78 + 2 * RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE,
|
|
entries: vec![
|
|
SmpRt3105SaveNameTableEntry {
|
|
index: 0,
|
|
offset: 0x7c78,
|
|
text: "Big Boy".to_string(),
|
|
availability_dword: 0,
|
|
availability_dword_hex: "0x00000000".to_string(),
|
|
trailer_word: 0,
|
|
trailer_word_hex: "0x00000000".to_string(),
|
|
},
|
|
SmpRt3105SaveNameTableEntry {
|
|
index: 1,
|
|
offset: 0x7cb9,
|
|
text: "GP7".to_string(),
|
|
availability_dword: 1,
|
|
availability_dword_hex: "0x00000001".to_string(),
|
|
trailer_word: 1,
|
|
trailer_word_hex: "0x00000001".to_string(),
|
|
},
|
|
],
|
|
evidence: vec![],
|
|
};
|
|
let bridge = SmpRt3105PostSpanBridgeProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
bridge_family: "rt3-105-save-post-span-bridge-v1".to_string(),
|
|
bridge_evidence: vec![],
|
|
span_target_offset: 0x3678,
|
|
next_candidate_offset: Some(0x4f14),
|
|
next_candidate_delta_from_span_target: Some(0x189c),
|
|
packed_profile_offset: 0x73c0,
|
|
packed_profile_delta_from_span_target: 0x3d48,
|
|
next_candidate_delta_from_packed_profile: Some(-0x24ac),
|
|
selector_high_u16: 0x7110,
|
|
selector_high_hex: "0x7110".to_string(),
|
|
descriptor_high_u16: 0x7801,
|
|
descriptor_high_hex: "0x7801".to_string(),
|
|
next_candidate_high_u16_words: vec![0x6200, 0x0000, 0xfff7, 0x5515],
|
|
next_candidate_high_hex_words: vec![],
|
|
};
|
|
report.rt3_105_packed_profile_probe = Some(packed_profile.clone());
|
|
report.rt3_105_save_name_table_probe = Some(name_table.clone());
|
|
report.rt3_105_save_named_locomotive_availability_probe =
|
|
Some(named_locomotive_table.clone());
|
|
report.save_load_summary = build_save_load_summary(
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
None,
|
|
Some(&bridge),
|
|
None,
|
|
Some(&packed_profile),
|
|
Some(&name_table),
|
|
);
|
|
|
|
let slice = load_save_slice_from_report(&report).expect("1.05 save slice");
|
|
assert_eq!(slice.mechanism_family, "rt3-105-save-post-span-bridge-v1");
|
|
assert_eq!(
|
|
slice
|
|
.profile
|
|
.as_ref()
|
|
.and_then(|profile| profile.map_path.as_deref()),
|
|
Some("Alternate USA.gmp")
|
|
);
|
|
assert_eq!(
|
|
slice
|
|
.candidate_availability_table
|
|
.as_ref()
|
|
.expect("candidate table")
|
|
.entries[1]
|
|
.text,
|
|
"Uranium Mine"
|
|
);
|
|
assert_eq!(
|
|
slice
|
|
.named_locomotive_availability_table
|
|
.as_ref()
|
|
.expect("named locomotive availability table")
|
|
.entries[1]
|
|
.text,
|
|
"GP7"
|
|
);
|
|
assert_eq!(
|
|
slice
|
|
.locomotive_catalog
|
|
.as_ref()
|
|
.expect("derived locomotive catalog")
|
|
.entries[0]
|
|
.name,
|
|
"Big Boy"
|
|
);
|
|
assert_eq!(
|
|
slice
|
|
.locomotive_catalog
|
|
.as_ref()
|
|
.expect("derived locomotive catalog")
|
|
.entries[1]
|
|
.locomotive_id,
|
|
2
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_save_world_selection_context_probe_from_fixed_world_block() {
|
|
let mut bytes = vec![0u8; 0x8000];
|
|
let chunk_tag_offset = 0x3ceusize;
|
|
let payload_offset = chunk_tag_offset + 4;
|
|
bytes[chunk_tag_offset..chunk_tag_offset + 4]
|
|
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_CHUNK_TAG.to_le_bytes());
|
|
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET
|
|
..payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_COMPANY_ID_RELATIVE_OFFSET + 4]
|
|
.copy_from_slice(&7u32.to_le_bytes());
|
|
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET
|
|
..payload_offset
|
|
+ RT3_SAVE_WORLD_BLOCK_SELECTED_CHAIRMAN_PROFILE_ID_RELATIVE_OFFSET
|
|
+ 4]
|
|
.copy_from_slice(&9u32.to_le_bytes());
|
|
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_SELECTOR_RELATIVE_OFFSET
|
|
..payload_offset
|
|
+ RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_SELECTOR_RELATIVE_OFFSET
|
|
+ RT3_SAVE_WORLD_BLOCK_CHAIRMAN_SLOT_COUNT]
|
|
.copy_from_slice(&[3, 1, 4, 1, 5, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
|
|
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_CAMPAIGN_OVERRIDE_FLAG_RELATIVE_OFFSET] = 1;
|
|
for (slot_index, role_gate) in [2u8, 1, 0, 2].into_iter().enumerate() {
|
|
bytes[payload_offset
|
|
+ RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_RELATIVE_OFFSET
|
|
+ slot_index * RT3_SAVE_WORLD_BLOCK_CHAIRMAN_ROLE_GATE_STRIDE] = role_gate;
|
|
}
|
|
let next_chunk_offset = payload_offset + RT3_SAVE_WORLD_BLOCK_LEN;
|
|
bytes[next_chunk_offset..next_chunk_offset + 4]
|
|
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG.to_le_bytes());
|
|
|
|
let probe = parse_save_world_selection_context_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
)
|
|
.expect("selection-context probe should parse");
|
|
|
|
assert_eq!(probe.chunk_tag_offset, chunk_tag_offset);
|
|
assert_eq!(probe.payload_offset, payload_offset);
|
|
assert_eq!(probe.selected_company_id, 7);
|
|
assert_eq!(probe.selected_chairman_profile_id, 9);
|
|
assert_eq!(probe.chairman_slot_selectors[..6], [3, 1, 4, 1, 5, 9]);
|
|
assert_eq!(probe.campaign_override_flag, 1);
|
|
assert_eq!(probe.chairman_role_gate_bytes[..4], [2, 1, 0, 2]);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_save_world_economic_tuning_probe_from_fixed_world_block() {
|
|
let mut bytes = vec![0u8; 0x8000];
|
|
let chunk_tag_offset = 0x3ceusize;
|
|
let payload_offset = chunk_tag_offset + 4;
|
|
bytes[chunk_tag_offset..chunk_tag_offset + 4]
|
|
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_CHUNK_TAG.to_le_bytes());
|
|
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET
|
|
..payload_offset + RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_MIRROR_RELATIVE_OFFSET + 4]
|
|
.copy_from_slice(&1.5f32.to_bits().to_le_bytes());
|
|
for (lane_index, relative_offset) in
|
|
RT3_SAVE_WORLD_BLOCK_ECONOMIC_TUNING_PRIMARY_RELATIVE_OFFSETS
|
|
.iter()
|
|
.copied()
|
|
.enumerate()
|
|
{
|
|
let value = (lane_index as f32) + 10.25f32;
|
|
bytes[payload_offset + relative_offset..payload_offset + relative_offset + 4]
|
|
.copy_from_slice(&value.to_bits().to_le_bytes());
|
|
}
|
|
let next_chunk_offset = payload_offset + RT3_SAVE_WORLD_BLOCK_LEN;
|
|
bytes[next_chunk_offset..next_chunk_offset + 4]
|
|
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG.to_le_bytes());
|
|
|
|
let probe = parse_save_world_economic_tuning_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
)
|
|
.expect("world economic tuning probe should parse");
|
|
|
|
assert_eq!(probe.chunk_tag_offset, chunk_tag_offset);
|
|
assert_eq!(probe.payload_offset, payload_offset);
|
|
assert_eq!(probe.mirror_lane.relative_offset_hex, "0xbda");
|
|
assert_eq!(probe.mirror_lane.value_f32, 1.5);
|
|
assert_eq!(probe.tuning_lanes.len(), 6);
|
|
assert_eq!(probe.tuning_lanes[0].relative_offset_hex, "0xbde");
|
|
assert_eq!(probe.tuning_lanes[0].value_f32, 10.25);
|
|
assert_eq!(probe.tuning_lanes[5].relative_offset_hex, "0xbf2");
|
|
assert_eq!(probe.tuning_lanes[5].value_f32, 15.25);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_save_world_issue_37_probe_from_fixed_world_block() {
|
|
let mut bytes = vec![0u8; 0x8000];
|
|
let chunk_tag_offset = 0x3ceusize;
|
|
let payload_offset = chunk_tag_offset + 4;
|
|
bytes[chunk_tag_offset..chunk_tag_offset + 4]
|
|
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_CHUNK_TAG.to_le_bytes());
|
|
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET
|
|
..payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_VALUE_RELATIVE_OFFSET + 4]
|
|
.copy_from_slice(&3u32.to_le_bytes());
|
|
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_MULTIPLIER_RELATIVE_OFFSET
|
|
..payload_offset + RT3_SAVE_WORLD_BLOCK_ISSUE_0X37_MULTIPLIER_RELATIVE_OFFSET + 4]
|
|
.copy_from_slice(&0.06f32.to_bits().to_le_bytes());
|
|
let next_chunk_offset = payload_offset + RT3_SAVE_WORLD_BLOCK_LEN;
|
|
bytes[next_chunk_offset..next_chunk_offset + 4]
|
|
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG.to_le_bytes());
|
|
|
|
let probe = parse_save_world_issue_37_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
)
|
|
.expect("world issue-0x37 probe should parse");
|
|
|
|
assert_eq!(probe.chunk_tag_offset, chunk_tag_offset);
|
|
assert_eq!(probe.payload_offset, payload_offset);
|
|
assert_eq!(probe.issue_value_lane.relative_offset_hex, "0x29");
|
|
assert_eq!(probe.issue_value_lane.value_i32, 3);
|
|
assert_eq!(probe.issue_37_raw_u8, 3);
|
|
assert_eq!(probe.issue_37_raw_hex, "0x03");
|
|
assert_eq!(probe.issue_38_raw_u8, 0);
|
|
assert_eq!(probe.issue_38_raw_hex, "0x00");
|
|
assert_eq!(probe.issue_39_raw_u8, 0);
|
|
assert_eq!(probe.issue_39_raw_hex, "0x00");
|
|
assert_eq!(probe.issue_3a_raw_u8, 0);
|
|
assert_eq!(probe.issue_3a_raw_hex, "0x00");
|
|
assert_eq!(probe.multiplier_lane.relative_offset_hex, "0x25");
|
|
assert!((probe.multiplier_lane.value_f32 - 0.06).abs() < f32::EPSILON);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_save_world_finance_neighborhood_probe_from_fixed_world_block() {
|
|
let mut bytes = vec![0u8; 0x8000];
|
|
let chunk_tag_offset = 0x3ceusize;
|
|
let payload_offset = chunk_tag_offset + 4;
|
|
bytes[chunk_tag_offset..chunk_tag_offset + 4]
|
|
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_CHUNK_TAG.to_le_bytes());
|
|
for index in 0..RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_WINDOW_LEN_DWORDS {
|
|
let relative_offset =
|
|
RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_ROOT_RELATIVE_OFFSET + index * 4;
|
|
bytes[payload_offset + relative_offset..payload_offset + relative_offset + 4]
|
|
.copy_from_slice(&((index as u32) + 1).to_le_bytes());
|
|
}
|
|
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_STOCK_POLICY_RELATIVE_OFFSET] = 1;
|
|
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_BOND_POLICY_RELATIVE_OFFSET] = 2;
|
|
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_BANKRUPTCY_POLICY_RELATIVE_OFFSET] = 3;
|
|
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_DIVIDEND_POLICY_RELATIVE_OFFSET] = 4;
|
|
bytes[payload_offset + RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET
|
|
..payload_offset + RT3_SAVE_WORLD_BLOCK_BUILDING_DENSITY_GROWTH_RELATIVE_OFFSET + 4]
|
|
.copy_from_slice(&2u32.to_le_bytes());
|
|
let next_chunk_offset = payload_offset + RT3_SAVE_WORLD_BLOCK_LEN;
|
|
bytes[next_chunk_offset..next_chunk_offset + 4]
|
|
.copy_from_slice(&RT3_SAVE_WORLD_BLOCK_NEXT_CHUNK_TAG.to_le_bytes());
|
|
|
|
let probe = parse_save_world_finance_neighborhood_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
)
|
|
.expect("world finance neighborhood probe should parse");
|
|
|
|
assert_eq!(probe.chunk_tag_offset, chunk_tag_offset);
|
|
assert_eq!(probe.payload_offset, payload_offset);
|
|
assert_eq!(
|
|
probe.dword_candidates.len(),
|
|
RT3_SAVE_WORLD_BLOCK_FINANCE_NEIGHBORHOOD_WINDOW_LEN_DWORDS
|
|
);
|
|
assert_eq!(
|
|
probe.current_calendar_tuple_word_lane.relative_offset_hex,
|
|
"0xd"
|
|
);
|
|
assert_eq!(probe.packed_year_word_raw_u16, 1);
|
|
assert_eq!(probe.packed_year_word_raw_hex, "0x0001");
|
|
assert_eq!(probe.partial_year_progress_raw_u8, 0);
|
|
assert_eq!(probe.partial_year_progress_raw_hex, "0x00");
|
|
assert_eq!(probe.stock_policy_raw_u8, 1);
|
|
assert_eq!(probe.stock_policy_raw_hex, "0x01");
|
|
assert_eq!(probe.bond_policy_raw_u8, 2);
|
|
assert_eq!(probe.bond_policy_raw_hex, "0x02");
|
|
assert_eq!(probe.bankruptcy_policy_raw_u8, 3);
|
|
assert_eq!(probe.bankruptcy_policy_raw_hex, "0x03");
|
|
assert_eq!(probe.dividend_policy_raw_u8, 4);
|
|
assert_eq!(probe.dividend_policy_raw_hex, "0x04");
|
|
assert_eq!(probe.building_density_growth_setting_lane.raw_u32, 2);
|
|
assert_eq!(
|
|
probe
|
|
.building_density_growth_setting_lane
|
|
.relative_offset_hex,
|
|
"0x4c78"
|
|
);
|
|
assert_eq!(probe.current_calendar_tuple_word_lane.value_i32, 1);
|
|
assert_eq!(
|
|
probe.current_calendar_tuple_word_2_lane.relative_offset_hex,
|
|
"0x11"
|
|
);
|
|
assert_eq!(probe.current_calendar_tuple_word_2_lane.value_i32, 2);
|
|
assert_eq!(probe.absolute_counter_lane.relative_offset_hex, "0x15");
|
|
assert_eq!(probe.absolute_counter_lane.value_i32, 3);
|
|
assert_eq!(
|
|
probe.absolute_counter_mirror_lane.relative_offset_hex,
|
|
"0x19"
|
|
);
|
|
assert_eq!(probe.absolute_counter_mirror_lane.value_i32, 4);
|
|
assert_eq!(
|
|
probe.dword_candidates[0].label,
|
|
"current_calendar_tuple_word"
|
|
);
|
|
assert_eq!(probe.dword_candidates[0].relative_offset_hex, "0xd");
|
|
assert_eq!(probe.dword_candidates[0].value_i32, 1);
|
|
assert_eq!(probe.dword_candidates[6].label, "issue_0x37_multiplier");
|
|
assert_eq!(probe.dword_candidates[6].relative_offset_hex, "0x25");
|
|
assert_eq!(
|
|
probe.dword_candidates[10].label,
|
|
"issue_neighbor_candidate_2"
|
|
);
|
|
assert_eq!(probe.dword_candidates[10].relative_offset_hex, "0x35");
|
|
assert_eq!(probe.dword_candidates[10].value_i32, 11);
|
|
assert_eq!(
|
|
probe.dword_candidates[11].label,
|
|
"finance_neighborhood_word_12"
|
|
);
|
|
assert_eq!(probe.dword_candidates[11].relative_offset_hex, "0x39");
|
|
assert_eq!(probe.dword_candidates[11].value_i32, 12);
|
|
assert_eq!(
|
|
probe.dword_candidates[16].label,
|
|
"finance_neighborhood_word_17"
|
|
);
|
|
assert_eq!(probe.dword_candidates[16].relative_offset_hex, "0x4d");
|
|
assert_eq!(probe.dword_candidates[16].value_i32, 17);
|
|
}
|
|
|
|
#[test]
|
|
fn loads_selection_only_company_and_chairman_context_from_save_world_probe() {
|
|
let mut report = inspect_smp_bytes(&[]);
|
|
report.save_load_summary = Some(SmpSaveLoadSummary {
|
|
file_extension_hint: Some("gms".to_string()),
|
|
container_profile_family: Some("rt3-105-save-container-v1".to_string()),
|
|
mechanism_family: "rt3-105-save-post-span-bridge-v1".to_string(),
|
|
mechanism_confidence: "mixed".to_string(),
|
|
packed_profile_kind: None,
|
|
packed_profile_family: None,
|
|
packed_profile_offset: None,
|
|
packed_profile_len: None,
|
|
map_path: None,
|
|
display_name: None,
|
|
profile_byte_0x77: None,
|
|
profile_byte_0x77_hex: None,
|
|
profile_byte_0x82: None,
|
|
profile_byte_0x82_hex: None,
|
|
profile_byte_0x97: None,
|
|
profile_byte_0x97_hex: None,
|
|
profile_byte_0xc5: None,
|
|
profile_byte_0xc5_hex: None,
|
|
trailer_family: Some("rt3-105-save-trailer-v1".to_string()),
|
|
bridge_family: Some("rt3-105-save-post-span-bridge-v1".to_string()),
|
|
candidate_table: None,
|
|
notes: vec![],
|
|
});
|
|
report.save_world_selection_context_probe = Some(SmpSaveWorldSelectionContextProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-direct-world-block".to_string(),
|
|
semantic_family: "scenario-selected-company-and-chairman-context".to_string(),
|
|
chunk_tag_offset: 0x3ce,
|
|
payload_offset: 0x3d2,
|
|
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
|
|
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
|
|
selected_company_id_offset: 0x3ef,
|
|
selected_company_id: 1,
|
|
selected_company_id_hex: "0x00000001".to_string(),
|
|
selected_chairman_profile_id_offset: 0x3f3,
|
|
selected_chairman_profile_id: 9,
|
|
selected_chairman_profile_id_hex: "0x00000009".to_string(),
|
|
chairman_slot_selector_offset: 0x455,
|
|
chairman_slot_selectors: vec![3, 1, 4, 1, 5, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
campaign_override_flag_offset: 0x493,
|
|
campaign_override_flag: 1,
|
|
campaign_override_flag_hex: "0x01".to_string(),
|
|
chairman_role_gate_offset: 0xf91,
|
|
chairman_role_gate_bytes: vec![2, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
evidence: vec![],
|
|
});
|
|
report.save_world_issue_37_probe = Some(SmpSaveWorldIssue37Probe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-direct-world-block".to_string(),
|
|
semantic_family: "scenario-save-world-issue-0x37".to_string(),
|
|
chunk_tag_offset: 0x3ce,
|
|
payload_offset: 0x3d2,
|
|
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
|
|
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
|
|
issue_37_raw_u8: 3,
|
|
issue_37_raw_hex: "0x03".to_string(),
|
|
issue_38_raw_u8: 1,
|
|
issue_38_raw_hex: "0x01".to_string(),
|
|
issue_39_raw_u8: 2,
|
|
issue_39_raw_hex: "0x02".to_string(),
|
|
issue_3a_raw_u8: 4,
|
|
issue_3a_raw_hex: "0x04".to_string(),
|
|
issue_value_lane: SmpSaveDwordCandidate {
|
|
label: "issue_0x37_value".to_string(),
|
|
relative_offset: 0x29,
|
|
relative_offset_hex: "0x29".to_string(),
|
|
raw_u32: 3,
|
|
raw_u32_hex: "0x00000003".to_string(),
|
|
value_i32: 3,
|
|
value_f32: f32::from_bits(3),
|
|
},
|
|
multiplier_lane: SmpSaveDwordCandidate {
|
|
label: "issue_0x37_multiplier".to_string(),
|
|
relative_offset: 0x25,
|
|
relative_offset_hex: "0x25".to_string(),
|
|
raw_u32: 0x3d75c28f,
|
|
raw_u32_hex: "0x3d75c28f".to_string(),
|
|
value_i32: 1031127695,
|
|
value_f32: 0.06,
|
|
},
|
|
issue_opinion_base_terms_raw_i32: Vec::new(),
|
|
evidence: vec![],
|
|
});
|
|
report.save_world_economic_tuning_probe = Some(SmpSaveWorldEconomicTuningProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-direct-world-block".to_string(),
|
|
semantic_family: "scenario-save-world-economic-tuning".to_string(),
|
|
chunk_tag_offset: 0x3ce,
|
|
payload_offset: 0x3d2,
|
|
payload_len: RT3_SAVE_WORLD_BLOCK_LEN,
|
|
payload_len_hex: format!("0x{:x}", RT3_SAVE_WORLD_BLOCK_LEN),
|
|
mirror_lane: SmpSaveDwordCandidate {
|
|
label: "economic_tuning_mirror_lane_0".to_string(),
|
|
relative_offset: 0xbda,
|
|
relative_offset_hex: "0xbda".to_string(),
|
|
raw_u32: 0x3f46d093,
|
|
raw_u32_hex: "0x3f46d093".to_string(),
|
|
value_i32: 1061605523,
|
|
value_f32: 0.7766201,
|
|
},
|
|
tuning_lanes: vec![
|
|
SmpSaveDwordCandidate {
|
|
label: "economic_tuning_lane_0".to_string(),
|
|
relative_offset: 0xbde,
|
|
relative_offset_hex: "0xbde".to_string(),
|
|
raw_u32: 0x3f400000,
|
|
raw_u32_hex: "0x3f400000".to_string(),
|
|
value_i32: 1061158912,
|
|
value_f32: 0.75,
|
|
},
|
|
SmpSaveDwordCandidate {
|
|
label: "economic_tuning_lane_1".to_string(),
|
|
relative_offset: 0xbe2,
|
|
relative_offset_hex: "0xbe2".to_string(),
|
|
raw_u32: 0x3be56042,
|
|
raw_u32_hex: "0x3be56042".to_string(),
|
|
value_i32: 1004888130,
|
|
value_f32: 0.007,
|
|
},
|
|
],
|
|
evidence: vec![],
|
|
});
|
|
report.save_company_collection_header_probe = Some(SmpSaveTaggedCollectionHeaderProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-company-tagged-header-counts".to_string(),
|
|
semantic_family: "scenario-save-company-header-counts".to_string(),
|
|
metadata_tag_offset: 0x1000,
|
|
records_tag_offset: 0x1100,
|
|
close_tag_offset: 0x1200,
|
|
direct_collection_flag: 1,
|
|
direct_collection_flag_hex: "0x00000001".to_string(),
|
|
direct_record_stride: 0x7684,
|
|
direct_record_stride_hex: "0x00007684".to_string(),
|
|
live_id_bound: 5,
|
|
live_id_bound_hex: "0x00000005".to_string(),
|
|
live_record_count: 1,
|
|
live_record_count_hex: "0x00000001".to_string(),
|
|
header_words: vec![1, 0x7684, 5, 5, 5, 1],
|
|
header_hex_words: vec![
|
|
"0x00000001".to_string(),
|
|
"0x00007684".to_string(),
|
|
"0x00000005".to_string(),
|
|
"0x00000005".to_string(),
|
|
"0x00000005".to_string(),
|
|
"0x00000001".to_string(),
|
|
],
|
|
evidence: vec![],
|
|
});
|
|
report.save_chairman_profile_collection_header_probe =
|
|
Some(SmpSaveTaggedCollectionHeaderProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-chairman-profile-tagged-header-counts".to_string(),
|
|
semantic_family: "scenario-save-chairman-profile-header-counts".to_string(),
|
|
metadata_tag_offset: 0x2000,
|
|
records_tag_offset: 0x2100,
|
|
close_tag_offset: 0x2200,
|
|
direct_collection_flag: 1,
|
|
direct_collection_flag_hex: "0x00000001".to_string(),
|
|
direct_record_stride: 0xcab,
|
|
direct_record_stride_hex: "0x00000cab".to_string(),
|
|
live_id_bound: 8,
|
|
live_id_bound_hex: "0x00000008".to_string(),
|
|
live_record_count: 2,
|
|
live_record_count_hex: "0x00000002".to_string(),
|
|
header_words: vec![1, 0xcab, 8, 6, 8, 2],
|
|
header_hex_words: vec![
|
|
"0x00000001".to_string(),
|
|
"0x00000cab".to_string(),
|
|
"0x00000008".to_string(),
|
|
"0x00000006".to_string(),
|
|
"0x00000008".to_string(),
|
|
"0x00000002".to_string(),
|
|
],
|
|
evidence: vec![],
|
|
});
|
|
report.save_train_collection_header_probe = Some(SmpSaveTaggedCollectionHeaderProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-train-tagged-header-counts".to_string(),
|
|
semantic_family: "scenario-save-train-header-counts".to_string(),
|
|
metadata_tag_offset: 0x3000,
|
|
records_tag_offset: 0x3100,
|
|
close_tag_offset: 0x3200,
|
|
direct_collection_flag: 1,
|
|
direct_collection_flag_hex: "0x00000001".to_string(),
|
|
direct_record_stride: 0x1d5,
|
|
direct_record_stride_hex: "0x000001d5".to_string(),
|
|
live_id_bound: 0x32,
|
|
live_id_bound_hex: "0x00000032".to_string(),
|
|
live_record_count: 0x14,
|
|
live_record_count_hex: "0x00000014".to_string(),
|
|
header_words: vec![1, 0x1d5, 0x32, 0x14, 0x32, 0x14],
|
|
header_hex_words: vec![
|
|
"0x00000001".to_string(),
|
|
"0x000001d5".to_string(),
|
|
"0x00000032".to_string(),
|
|
"0x00000014".to_string(),
|
|
"0x00000032".to_string(),
|
|
"0x00000014".to_string(),
|
|
],
|
|
evidence: vec![],
|
|
});
|
|
report.save_train_collection_directory_probe = Some(SmpSaveTrainCollectionDirectoryProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-train-live-directory".to_string(),
|
|
semantic_family: "scenario-save-train-live-directory".to_string(),
|
|
metadata_tag_offset: 0x3000,
|
|
records_tag_offset: 0x3100,
|
|
close_tag_offset: 0x3200,
|
|
directory_root_dword_index: 16,
|
|
directory_entry_dword_count: 3,
|
|
live_record_count: 0x14,
|
|
live_id_bound: 0x32,
|
|
chain_head_live_entry_id: Some(1),
|
|
chain_tail_live_entry_id: Some(20),
|
|
entries: vec![
|
|
SmpSaveTrainCollectionDirectoryEntryProbe {
|
|
live_entry_id: 1,
|
|
payload_relative_offset: 0x2af8,
|
|
payload_relative_offset_hex: "0x00002af8".to_string(),
|
|
payload_absolute_offset: 0x5afc,
|
|
previous_live_entry_id: 0,
|
|
previous_live_entry_id_hex: "0x00000000".to_string(),
|
|
next_live_entry_id: 2,
|
|
next_live_entry_id_hex: "0x00000002".to_string(),
|
|
},
|
|
SmpSaveTrainCollectionDirectoryEntryProbe {
|
|
live_entry_id: 2,
|
|
payload_relative_offset: 0x2ee0,
|
|
payload_relative_offset_hex: "0x00002ee0".to_string(),
|
|
payload_absolute_offset: 0x5ee4,
|
|
previous_live_entry_id: 1,
|
|
previous_live_entry_id_hex: "0x00000001".to_string(),
|
|
next_live_entry_id: 0,
|
|
next_live_entry_id_hex: "0x00000000".to_string(),
|
|
},
|
|
],
|
|
evidence: vec![],
|
|
});
|
|
report.save_region_collection_header_probe = Some(SmpSaveTaggedCollectionHeaderProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-region-tagged-header-counts".to_string(),
|
|
semantic_family: "scenario-save-region-header-counts".to_string(),
|
|
metadata_tag_offset: 0x5000,
|
|
records_tag_offset: 0x5100,
|
|
close_tag_offset: 0x5200,
|
|
direct_collection_flag: 0,
|
|
direct_collection_flag_hex: "0x00000000".to_string(),
|
|
direct_record_stride: 0x06,
|
|
direct_record_stride_hex: "0x00000006".to_string(),
|
|
live_id_bound: 0x96,
|
|
live_id_bound_hex: "0x00000096".to_string(),
|
|
live_record_count: 0x91,
|
|
live_record_count_hex: "0x00000091".to_string(),
|
|
header_words: vec![0, 6, 0x0a, 0x14, 0x96, 0x91],
|
|
header_hex_words: vec![
|
|
"0x00000000".to_string(),
|
|
"0x00000006".to_string(),
|
|
"0x0000000a".to_string(),
|
|
"0x00000014".to_string(),
|
|
"0x00000096".to_string(),
|
|
"0x00000091".to_string(),
|
|
],
|
|
evidence: vec![],
|
|
});
|
|
report.save_region_record_triplet_probe = Some(SmpSaveRegionRecordTripletProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-region-record-triplets".to_string(),
|
|
semantic_family: "scenario-save-region-record-triplets".to_string(),
|
|
records_tag_offset: 0x5100,
|
|
close_tag_offset: 0x5200,
|
|
record_count: 2,
|
|
entries: vec![
|
|
SmpSaveRegionRecordTripletEntryProbe {
|
|
record_index: 0,
|
|
name: "Marker09".to_string(),
|
|
record_payload_relative_offset: 0,
|
|
record_payload_relative_offset_hex: "0x0".to_string(),
|
|
name_tag_relative_offset: 0,
|
|
policy_tag_relative_offset: 0x10,
|
|
profile_tag_relative_offset: 0x2e,
|
|
pre_name_prefix_len: 0,
|
|
pre_name_prefix_hex_bytes: Vec::new(),
|
|
pre_name_prefix_dword_candidates: Vec::new(),
|
|
policy_chunk_len: 0x1a,
|
|
profile_chunk_len: 0x40,
|
|
policy_leading_f32_0: 368.0,
|
|
policy_leading_f32_1: 0.0,
|
|
policy_leading_f32_2: 92.0,
|
|
policy_reserved_dwords: vec![0, 0, 0],
|
|
policy_reserved_dword_candidates: Vec::new(),
|
|
policy_trailing_word: 1,
|
|
policy_trailing_word_hex: "0x0001".to_string(),
|
|
profile_collection: Some(SmpSaveRegionProfileCollectionProbe {
|
|
direct_collection_flag: 1,
|
|
entry_stride: 0x22,
|
|
live_id_bound: 18,
|
|
live_record_count: 17,
|
|
entry_start_relative_offset: 0x4d,
|
|
trailing_padding_len: 2,
|
|
entries: vec![
|
|
SmpSaveRegionProfileEntryProbe {
|
|
entry_index: 0,
|
|
row_relative_offset: 0x4d,
|
|
name: "House".to_string(),
|
|
trailing_weight_f32: 0.2,
|
|
},
|
|
SmpSaveRegionProfileEntryProbe {
|
|
entry_index: 1,
|
|
row_relative_offset: 0x6f,
|
|
name: "Farm Corn".to_string(),
|
|
trailing_weight_f32: 0.2,
|
|
},
|
|
],
|
|
}),
|
|
},
|
|
SmpSaveRegionRecordTripletEntryProbe {
|
|
record_index: 1,
|
|
name: "Marker10".to_string(),
|
|
record_payload_relative_offset: 0x6e,
|
|
record_payload_relative_offset_hex: "0x6e".to_string(),
|
|
name_tag_relative_offset: 0x6e,
|
|
policy_tag_relative_offset: 0x7e,
|
|
profile_tag_relative_offset: 0x9c,
|
|
pre_name_prefix_len: 0,
|
|
pre_name_prefix_hex_bytes: Vec::new(),
|
|
pre_name_prefix_dword_candidates: Vec::new(),
|
|
policy_chunk_len: 0x1a,
|
|
profile_chunk_len: 0x20,
|
|
policy_leading_f32_0: 552.0,
|
|
policy_leading_f32_1: 0.0,
|
|
policy_leading_f32_2: 276.0,
|
|
policy_reserved_dwords: vec![0, 0, 0],
|
|
policy_reserved_dword_candidates: Vec::new(),
|
|
policy_trailing_word: 1,
|
|
policy_trailing_word_hex: "0x0001".to_string(),
|
|
profile_collection: Some(SmpSaveRegionProfileCollectionProbe {
|
|
direct_collection_flag: 1,
|
|
entry_stride: 0x22,
|
|
live_id_bound: 26,
|
|
live_record_count: 24,
|
|
entry_start_relative_offset: 0x50,
|
|
trailing_padding_len: 0,
|
|
entries: vec![SmpSaveRegionProfileEntryProbe {
|
|
entry_index: 0,
|
|
row_relative_offset: 0x50,
|
|
name: "Farm Corn".to_string(),
|
|
trailing_weight_f32: 0.2,
|
|
}],
|
|
}),
|
|
},
|
|
],
|
|
evidence: vec![],
|
|
});
|
|
report.save_placed_structure_collection_header_probe =
|
|
Some(SmpSaveTaggedCollectionHeaderProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-placed-structure-tagged-header-counts".to_string(),
|
|
semantic_family: "scenario-save-placed-structure-header-counts".to_string(),
|
|
metadata_tag_offset: 0x4000,
|
|
records_tag_offset: 0x4100,
|
|
close_tag_offset: 0x4200,
|
|
direct_collection_flag: 0,
|
|
direct_collection_flag_hex: "0x00000000".to_string(),
|
|
direct_record_stride: 0x06,
|
|
direct_record_stride_hex: "0x00000006".to_string(),
|
|
live_id_bound: 0x7ee,
|
|
live_id_bound_hex: "0x000007ee".to_string(),
|
|
live_record_count: 0x7ea,
|
|
live_record_count_hex: "0x000007ea".to_string(),
|
|
header_words: vec![0, 6, 0x0a, 0x14, 0x7ee, 0x7ea],
|
|
header_hex_words: vec![
|
|
"0x00000000".to_string(),
|
|
"0x00000006".to_string(),
|
|
"0x0000000a".to_string(),
|
|
"0x00000014".to_string(),
|
|
"0x000007ee".to_string(),
|
|
"0x000007ea".to_string(),
|
|
],
|
|
evidence: vec![],
|
|
});
|
|
|
|
let slice = load_save_slice_from_report(&report).expect("save slice");
|
|
|
|
let company_roster = slice.company_roster.expect("selection-only company roster");
|
|
assert_eq!(company_roster.observed_entry_count, 1);
|
|
assert_eq!(company_roster.selected_company_id, Some(1));
|
|
assert!(company_roster.entries.is_empty());
|
|
|
|
let chairman_table = slice
|
|
.chairman_profile_table
|
|
.expect("selection-only chairman table");
|
|
assert_eq!(chairman_table.observed_entry_count, 2);
|
|
assert_eq!(chairman_table.selected_chairman_profile_id, Some(9));
|
|
assert!(chairman_table.entries.is_empty());
|
|
let issue_37_state = slice
|
|
.world_issue_37_state
|
|
.expect("world issue-0x37 state should load");
|
|
assert_eq!(issue_37_state.issue_value, 3);
|
|
assert_eq!(issue_37_state.issue_38_value, 1);
|
|
assert_eq!(issue_37_state.issue_39_value, 2);
|
|
assert_eq!(issue_37_state.issue_3a_value, 4);
|
|
assert_eq!(issue_37_state.multiplier_raw_hex, "0x3d75c28f");
|
|
assert_eq!(issue_37_state.multiplier_value_f32_text, "0.060000");
|
|
let tuning_state = slice
|
|
.world_economic_tuning_state
|
|
.expect("world economic tuning state should load");
|
|
assert_eq!(tuning_state.mirror_raw_hex, "0x3f46d093");
|
|
assert_eq!(tuning_state.mirror_value_f32_text, "0.776620");
|
|
assert_eq!(
|
|
tuning_state.lane_value_f32_text,
|
|
vec!["0.750000", "0.007000"]
|
|
);
|
|
|
|
assert!(
|
|
slice
|
|
.notes
|
|
.iter()
|
|
.any(|note| note.contains("selected_company_id=1"))
|
|
);
|
|
assert!(
|
|
slice
|
|
.notes
|
|
.iter()
|
|
.any(|note| note.contains("selected_chairman_profile_id=9"))
|
|
);
|
|
assert!(
|
|
slice
|
|
.notes
|
|
.iter()
|
|
.any(|note| note.contains("grounded issue-0x37 pair: value=3"))
|
|
);
|
|
assert!(
|
|
slice
|
|
.notes
|
|
.iter()
|
|
.any(|note| note.contains("campaign_override_flag=1"))
|
|
);
|
|
assert!(
|
|
slice
|
|
.notes
|
|
.iter()
|
|
.any(|note| note.contains("tagged company header reports live_record_count=1"))
|
|
);
|
|
assert!(slice.notes.iter().any(|note| {
|
|
note.contains("tagged chairman/profile header reports live_record_count=2")
|
|
}));
|
|
assert!(
|
|
slice
|
|
.notes
|
|
.iter()
|
|
.any(|note| note.contains("tagged train header reports live_record_count=20"))
|
|
);
|
|
assert!(slice.notes.iter().any(|note| {
|
|
note.contains("tagged train metadata also exposes a live-entry directory")
|
|
}));
|
|
assert!(
|
|
slice.notes.iter().any(|note| {
|
|
note.contains("tagged region header reports live_record_count=145")
|
|
})
|
|
);
|
|
assert!(slice.notes.iter().any(|note| {
|
|
note.contains(
|
|
"tagged region records also expose 2 repeated 0x55f1/0x55f2/0x55f3 triplets",
|
|
)
|
|
}));
|
|
assert!(slice.notes.iter().any(|note| {
|
|
note.contains("tagged placed-structure header reports live_record_count=2026")
|
|
}));
|
|
}
|
|
|
|
#[test]
|
|
fn parses_company_tagged_collection_header_probe_from_exact_u32_tags() {
|
|
let mut bytes = vec![0u8; 0x400];
|
|
let metadata_tag_offset = 0x40usize;
|
|
let records_tag_offset = 0x140usize;
|
|
let close_tag_offset = 0x180usize;
|
|
bytes[metadata_tag_offset..metadata_tag_offset + 4]
|
|
.copy_from_slice(&0x000061a9u32.to_le_bytes());
|
|
bytes[records_tag_offset..records_tag_offset + 4]
|
|
.copy_from_slice(&0x000061aau32.to_le_bytes());
|
|
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x000061abu32.to_le_bytes());
|
|
let header_words = [
|
|
1u32, 0x7684, 5, 5, 5, 1, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
|
];
|
|
for (index, word) in header_words.into_iter().enumerate() {
|
|
let offset = metadata_tag_offset + 4 + index * 4;
|
|
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
|
|
}
|
|
|
|
let probe = parse_save_company_collection_header_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
)
|
|
.expect("company header probe should parse");
|
|
|
|
assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
|
|
assert_eq!(probe.records_tag_offset, records_tag_offset);
|
|
assert_eq!(probe.close_tag_offset, close_tag_offset);
|
|
assert_eq!(probe.direct_record_stride, 0x7684);
|
|
assert_eq!(probe.live_id_bound, 5);
|
|
assert_eq!(probe.live_record_count, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_chairman_profile_tagged_collection_header_probe_from_exact_u32_tags() {
|
|
let mut bytes = vec![0u8; 0x400];
|
|
let metadata_tag_offset = 0x40usize;
|
|
let records_tag_offset = 0x140usize;
|
|
let close_tag_offset = 0x180usize;
|
|
bytes[metadata_tag_offset..metadata_tag_offset + 4]
|
|
.copy_from_slice(&0x00005209u32.to_le_bytes());
|
|
bytes[records_tag_offset..records_tag_offset + 4]
|
|
.copy_from_slice(&0x0000520au32.to_le_bytes());
|
|
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
|
|
let header_words = [
|
|
1u32, 0xcab, 8, 6, 8, 2, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
|
];
|
|
for (index, word) in header_words.into_iter().enumerate() {
|
|
let offset = metadata_tag_offset + 4 + index * 4;
|
|
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
|
|
}
|
|
|
|
let probe = parse_save_chairman_profile_collection_header_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
)
|
|
.expect("chairman profile header probe should parse");
|
|
|
|
assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
|
|
assert_eq!(probe.records_tag_offset, records_tag_offset);
|
|
assert_eq!(probe.close_tag_offset, close_tag_offset);
|
|
assert_eq!(probe.direct_record_stride, 0xcab);
|
|
assert_eq!(probe.live_id_bound, 8);
|
|
assert_eq!(probe.live_record_count, 2);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_train_tagged_collection_header_probe_from_exact_u32_tags() {
|
|
let mut bytes = vec![0u8; 0x400];
|
|
let metadata_tag_offset = 0x40usize;
|
|
let records_tag_offset = 0x140usize;
|
|
let close_tag_offset = 0x180usize;
|
|
bytes[metadata_tag_offset..metadata_tag_offset + 4]
|
|
.copy_from_slice(&0x00005209u32.to_le_bytes());
|
|
bytes[records_tag_offset..records_tag_offset + 4]
|
|
.copy_from_slice(&0x0000520au32.to_le_bytes());
|
|
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
|
|
let header_words = [
|
|
1u32, 0x1d5, 0x32, 0x14, 0x32, 0x14, 0x14, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
|
|
];
|
|
for (index, word) in header_words.into_iter().enumerate() {
|
|
let offset = metadata_tag_offset + 4 + index * 4;
|
|
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
|
|
}
|
|
|
|
let probe = parse_save_train_collection_header_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
)
|
|
.expect("train header probe should parse");
|
|
|
|
assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
|
|
assert_eq!(probe.records_tag_offset, records_tag_offset);
|
|
assert_eq!(probe.close_tag_offset, close_tag_offset);
|
|
assert_eq!(probe.direct_collection_flag, 1);
|
|
assert_eq!(probe.direct_record_stride, 0x1d5);
|
|
assert_eq!(probe.live_id_bound, 0x32);
|
|
assert_eq!(probe.live_record_count, 0x14);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_region_tagged_collection_header_probe_from_marker09_family() {
|
|
let mut bytes = vec![0u8; 0x400];
|
|
let metadata_tag_offset = 0x40usize;
|
|
let records_tag_offset = 0x180usize;
|
|
let close_tag_offset = 0x1c0usize;
|
|
bytes[metadata_tag_offset..metadata_tag_offset + 4]
|
|
.copy_from_slice(&0x00005209u32.to_le_bytes());
|
|
bytes[records_tag_offset..records_tag_offset + 4]
|
|
.copy_from_slice(&0x0000520au32.to_le_bytes());
|
|
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
|
|
let header_words = [
|
|
0u32, 0x06, 0x0a, 0x14, 0x96, 0x91, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
];
|
|
for (index, word) in header_words.into_iter().enumerate() {
|
|
let offset = metadata_tag_offset + 4 + index * 4;
|
|
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
|
|
}
|
|
let marker_offset = records_tag_offset + 4 + 0x20;
|
|
bytes[marker_offset..marker_offset + 8].copy_from_slice(b"Marker09");
|
|
|
|
let probe = parse_save_region_collection_header_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
)
|
|
.expect("region header probe should parse");
|
|
|
|
assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
|
|
assert_eq!(probe.records_tag_offset, records_tag_offset);
|
|
assert_eq!(probe.close_tag_offset, close_tag_offset);
|
|
assert_eq!(probe.direct_collection_flag, 0);
|
|
assert_eq!(probe.direct_record_stride, 0x06);
|
|
assert_eq!(probe.live_id_bound, 0x96);
|
|
assert_eq!(probe.live_record_count, 0x91);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_train_collection_directory_probe_from_tagged_metadata_triplets() {
|
|
let mut bytes = vec![0u8; 0x400];
|
|
let metadata_tag_offset = 0x40usize;
|
|
let records_tag_offset = 0x180usize;
|
|
let close_tag_offset = 0x1c0usize;
|
|
bytes[metadata_tag_offset..metadata_tag_offset + 4]
|
|
.copy_from_slice(&0x00005209u32.to_le_bytes());
|
|
bytes[records_tag_offset..records_tag_offset + 4]
|
|
.copy_from_slice(&0x0000520au32.to_le_bytes());
|
|
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
|
|
let header_words = [
|
|
1u32, 0x1d5, 0x32, 0x03, 0x32, 0x03, 0x14, 1, 0, 0, 1, 1, 0x14, 0, 0, 0,
|
|
];
|
|
for (index, word) in header_words.into_iter().enumerate() {
|
|
let offset = metadata_tag_offset + 4 + index * 4;
|
|
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
|
|
}
|
|
let triplets = [(0x2af8u32, 0u32, 2u32), (0x2ee0, 1, 3), (0x32c8, 2, 0)];
|
|
for (index, (offset_word, prev, next)) in triplets.into_iter().enumerate() {
|
|
let base = metadata_tag_offset
|
|
+ 4
|
|
+ (SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX + index * 3) * 4;
|
|
bytes[base..base + 4].copy_from_slice(&offset_word.to_le_bytes());
|
|
bytes[base + 4..base + 8].copy_from_slice(&prev.to_le_bytes());
|
|
bytes[base + 8..base + 12].copy_from_slice(&next.to_le_bytes());
|
|
}
|
|
|
|
let header_probe = parse_save_train_collection_header_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
)
|
|
.expect("train header probe should parse");
|
|
let directory_probe =
|
|
parse_save_train_collection_directory_probe(&bytes, Some(&header_probe))
|
|
.expect("train directory probe should parse");
|
|
|
|
assert_eq!(directory_probe.directory_root_dword_index, 16);
|
|
assert_eq!(directory_probe.live_record_count, 3);
|
|
assert_eq!(directory_probe.chain_head_live_entry_id, Some(1));
|
|
assert_eq!(directory_probe.chain_tail_live_entry_id, Some(3));
|
|
assert_eq!(directory_probe.entries.len(), 3);
|
|
assert_eq!(directory_probe.entries[0].live_entry_id, 1);
|
|
assert_eq!(directory_probe.entries[0].payload_relative_offset, 0x2af8);
|
|
assert_eq!(directory_probe.entries[0].previous_live_entry_id, 0);
|
|
assert_eq!(directory_probe.entries[0].next_live_entry_id, 2);
|
|
assert_eq!(directory_probe.entries[2].live_entry_id, 3);
|
|
assert_eq!(directory_probe.entries[2].previous_live_entry_id, 2);
|
|
assert_eq!(directory_probe.entries[2].next_live_entry_id, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_region_record_triplet_probe_from_marker09_records() {
|
|
let mut bytes = vec![0u8; 0x400];
|
|
let metadata_tag_offset = 0x40usize;
|
|
let records_tag_offset = 0x140usize;
|
|
let close_tag_offset = 0x260usize;
|
|
bytes[metadata_tag_offset..metadata_tag_offset + 4]
|
|
.copy_from_slice(&0x00005209u32.to_le_bytes());
|
|
bytes[records_tag_offset..records_tag_offset + 4]
|
|
.copy_from_slice(&0x0000520au32.to_le_bytes());
|
|
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
|
|
let mut cursor = records_tag_offset + 4;
|
|
for (name, x, density, y) in [
|
|
("Marker09", 368.0f32, 0.0f32, 92.0f32),
|
|
("Marker10", 552.0f32, 1.5f32, 276.0f32),
|
|
] {
|
|
bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_NAME_TAG.to_le_bytes());
|
|
bytes[cursor + 4] = name.len() as u8;
|
|
bytes[cursor + 5..cursor + 5 + name.len()].copy_from_slice(name.as_bytes());
|
|
cursor += 0x10;
|
|
bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_POLICY_TAG.to_le_bytes());
|
|
bytes[cursor + 4..cursor + 8].copy_from_slice(&x.to_bits().to_le_bytes());
|
|
bytes[cursor + 8..cursor + 12].copy_from_slice(&density.to_bits().to_le_bytes());
|
|
bytes[cursor + 12..cursor + 16].copy_from_slice(&y.to_bits().to_le_bytes());
|
|
bytes[cursor + 28..cursor + 30].copy_from_slice(&1u16.to_le_bytes());
|
|
cursor += 0x1e;
|
|
bytes[cursor..cursor + 2]
|
|
.copy_from_slice(&SAVE_REGION_RECORD_PROFILE_TAG.to_le_bytes());
|
|
cursor += 0x40;
|
|
}
|
|
|
|
let header_probe = SmpSaveTaggedCollectionHeaderProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-region-tagged-header-counts".to_string(),
|
|
semantic_family: "scenario-save-region-header-counts".to_string(),
|
|
metadata_tag_offset,
|
|
records_tag_offset,
|
|
close_tag_offset,
|
|
direct_collection_flag: 0,
|
|
direct_collection_flag_hex: "0x00000000".to_string(),
|
|
direct_record_stride: 0x06,
|
|
direct_record_stride_hex: "0x00000006".to_string(),
|
|
live_id_bound: 0x96,
|
|
live_id_bound_hex: "0x00000096".to_string(),
|
|
live_record_count: 2,
|
|
live_record_count_hex: "0x00000002".to_string(),
|
|
header_words: vec![0, 6, 0x0a, 0x14, 0x96, 0x02],
|
|
header_hex_words: vec![],
|
|
evidence: vec![],
|
|
};
|
|
let triplet_probe = parse_save_region_record_triplet_probe(&bytes, Some(&header_probe))
|
|
.expect("region triplet probe should parse");
|
|
|
|
assert_eq!(triplet_probe.record_count, 2);
|
|
assert_eq!(triplet_probe.entries[0].name, "Marker09");
|
|
assert_eq!(triplet_probe.entries[0].policy_tag_relative_offset, 0x10);
|
|
assert_eq!(triplet_probe.entries[0].profile_tag_relative_offset, 0x2e);
|
|
assert_eq!(triplet_probe.entries[0].policy_leading_f32_0, 368.0);
|
|
assert_eq!(triplet_probe.entries[0].policy_leading_f32_1, 0.0);
|
|
assert_eq!(triplet_probe.entries[0].policy_leading_f32_2, 92.0);
|
|
assert!(
|
|
triplet_probe.entries[0]
|
|
.pre_name_prefix_dword_candidates
|
|
.is_empty()
|
|
);
|
|
assert_eq!(
|
|
triplet_probe.entries[0].policy_reserved_dwords,
|
|
vec![0, 0, 0]
|
|
);
|
|
assert_eq!(
|
|
triplet_probe.entries[0]
|
|
.policy_reserved_dword_candidates
|
|
.len(),
|
|
3
|
|
);
|
|
assert_eq!(
|
|
triplet_probe.entries[0].policy_reserved_dword_candidates[0].relative_offset_hex,
|
|
"0x20"
|
|
);
|
|
assert_eq!(triplet_probe.entries[0].policy_trailing_word, 1);
|
|
assert_eq!(triplet_probe.entries[1].name, "Marker10");
|
|
assert_eq!(triplet_probe.entries[1].policy_leading_f32_0, 552.0);
|
|
assert_eq!(triplet_probe.entries[1].policy_leading_f32_1, 1.5);
|
|
assert_eq!(triplet_probe.entries[1].policy_leading_f32_2, 276.0);
|
|
assert!(
|
|
triplet_probe.entries[1]
|
|
.pre_name_prefix_dword_candidates
|
|
.is_empty()
|
|
);
|
|
assert_eq!(
|
|
triplet_probe.entries[1]
|
|
.policy_reserved_dword_candidates
|
|
.len(),
|
|
3
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_region_record_triplet_prefix_dword_candidates() {
|
|
let mut bytes = vec![0u8; 0x320];
|
|
let metadata_tag_offset = 0x0usize;
|
|
let records_tag_offset = 0x100usize;
|
|
let close_tag_offset = 0x200usize;
|
|
bytes[metadata_tag_offset..metadata_tag_offset + 4]
|
|
.copy_from_slice(&0x00005209u32.to_le_bytes());
|
|
bytes[records_tag_offset..records_tag_offset + 4]
|
|
.copy_from_slice(&0x0000520au32.to_le_bytes());
|
|
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
|
|
let mut cursor = records_tag_offset + 4;
|
|
let first_record_relative_offset = 0usize;
|
|
bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_NAME_TAG.to_le_bytes());
|
|
bytes[cursor + 4] = 8;
|
|
bytes[cursor + 5..cursor + 13].copy_from_slice(b"Marker11");
|
|
cursor += 0x10;
|
|
bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_POLICY_TAG.to_le_bytes());
|
|
bytes[cursor + 4..cursor + 8].copy_from_slice(&100.0f32.to_bits().to_le_bytes());
|
|
bytes[cursor + 8..cursor + 12].copy_from_slice(&2.0f32.to_bits().to_le_bytes());
|
|
bytes[cursor + 12..cursor + 16].copy_from_slice(&50.0f32.to_bits().to_le_bytes());
|
|
bytes[cursor + 28..cursor + 30].copy_from_slice(&1u16.to_le_bytes());
|
|
cursor += 0x1e;
|
|
bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_PROFILE_TAG.to_le_bytes());
|
|
cursor += 0x20;
|
|
let second_record_relative_offset = cursor - (records_tag_offset + 4);
|
|
bytes[cursor..cursor + 4].copy_from_slice(&0x11223344u32.to_le_bytes());
|
|
bytes[cursor + 4..cursor + 8].copy_from_slice(&0x55667788u32.to_le_bytes());
|
|
cursor += 8;
|
|
bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_NAME_TAG.to_le_bytes());
|
|
bytes[cursor + 4] = 8;
|
|
bytes[cursor + 5..cursor + 13].copy_from_slice(b"Marker12");
|
|
cursor += 0x10;
|
|
bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_POLICY_TAG.to_le_bytes());
|
|
bytes[cursor + 4..cursor + 8].copy_from_slice(&120.0f32.to_bits().to_le_bytes());
|
|
bytes[cursor + 8..cursor + 12].copy_from_slice(&3.0f32.to_bits().to_le_bytes());
|
|
bytes[cursor + 12..cursor + 16].copy_from_slice(&60.0f32.to_bits().to_le_bytes());
|
|
bytes[cursor + 16..cursor + 20].copy_from_slice(&0x01020304u32.to_le_bytes());
|
|
bytes[cursor + 20..cursor + 24].copy_from_slice(&0u32.to_le_bytes());
|
|
bytes[cursor + 24..cursor + 28].copy_from_slice(&0x05060708u32.to_le_bytes());
|
|
bytes[cursor + 28..cursor + 30].copy_from_slice(&1u16.to_le_bytes());
|
|
cursor += 0x1e;
|
|
bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_PROFILE_TAG.to_le_bytes());
|
|
let directory_root_byte_offset = SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX * 4;
|
|
let first_payload_relative_offset = records_tag_offset - metadata_tag_offset;
|
|
let second_payload_relative_offset =
|
|
first_payload_relative_offset + second_record_relative_offset;
|
|
bytes[metadata_tag_offset + 4 + directory_root_byte_offset
|
|
..metadata_tag_offset + 8 + directory_root_byte_offset]
|
|
.copy_from_slice(&(first_payload_relative_offset as u32).to_le_bytes());
|
|
bytes[metadata_tag_offset + 16 + directory_root_byte_offset
|
|
..metadata_tag_offset + 20 + directory_root_byte_offset]
|
|
.copy_from_slice(&(second_payload_relative_offset as u32).to_le_bytes());
|
|
|
|
let header_probe = SmpSaveTaggedCollectionHeaderProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-region-tagged-header-counts".to_string(),
|
|
semantic_family: "scenario-save-region-header-counts".to_string(),
|
|
metadata_tag_offset,
|
|
records_tag_offset,
|
|
close_tag_offset,
|
|
direct_collection_flag: 0,
|
|
direct_collection_flag_hex: "0x00000000".to_string(),
|
|
direct_record_stride: 0x06,
|
|
direct_record_stride_hex: "0x00000006".to_string(),
|
|
live_id_bound: 0x96,
|
|
live_id_bound_hex: "0x00000096".to_string(),
|
|
live_record_count: 2,
|
|
live_record_count_hex: "0x00000002".to_string(),
|
|
header_words: vec![0, 6, 0x0a, 0x14, 0x96, 0x02],
|
|
header_hex_words: vec![],
|
|
evidence: vec![],
|
|
};
|
|
let triplet_probe = parse_save_region_record_triplet_probe(&bytes, Some(&header_probe))
|
|
.expect("region triplet probe should parse");
|
|
|
|
assert_eq!(triplet_probe.entries.len(), 2);
|
|
assert_eq!(
|
|
triplet_probe.entries[0].record_payload_relative_offset,
|
|
first_record_relative_offset
|
|
);
|
|
assert_eq!(triplet_probe.entries[0].pre_name_prefix_len, 0);
|
|
assert_eq!(
|
|
triplet_probe.entries[1].record_payload_relative_offset,
|
|
second_record_relative_offset
|
|
);
|
|
assert_eq!(triplet_probe.entries[1].pre_name_prefix_len, 8);
|
|
assert_eq!(
|
|
triplet_probe.entries[1]
|
|
.pre_name_prefix_dword_candidates
|
|
.len(),
|
|
2
|
|
);
|
|
assert_eq!(
|
|
triplet_probe.entries[1].pre_name_prefix_dword_candidates[0].raw_u32_hex,
|
|
"0x11223344"
|
|
);
|
|
assert_eq!(
|
|
triplet_probe.entries[1].pre_name_prefix_dword_candidates[1].relative_offset_hex,
|
|
"0x52"
|
|
);
|
|
assert_eq!(
|
|
triplet_probe.entries[1].policy_reserved_dword_candidates[2].relative_offset_hex,
|
|
"0xcc"
|
|
);
|
|
assert_eq!(
|
|
triplet_probe.entries[1].policy_reserved_dword_candidates[0].raw_u32_hex,
|
|
"0x01020304"
|
|
);
|
|
assert_eq!(
|
|
triplet_probe.entries[1].policy_reserved_dword_candidates[2].raw_u32_hex,
|
|
"0x05060708"
|
|
);
|
|
assert!(triplet_probe.evidence.iter().any(|line| line.contains(
|
|
"fixed 0x55f2 policy reserved dwords are nonzero on 1 of 2 decoded region records"
|
|
)));
|
|
}
|
|
|
|
#[test]
|
|
fn parses_region_profile_collection_probe_from_fixed_name_rows() {
|
|
let mut payload = vec![0u8; 0x80];
|
|
let header_words = [1u32, 0x22, 2, 2, 3, 2, 0, 1];
|
|
for (index, word) in header_words.into_iter().enumerate() {
|
|
let offset = index * 4;
|
|
payload[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
|
|
}
|
|
let first_row_offset = 0x20usize;
|
|
let first_name = b"House";
|
|
payload[first_row_offset..first_row_offset + first_name.len()].copy_from_slice(first_name);
|
|
payload[first_row_offset + 0x1e..first_row_offset + 0x22]
|
|
.copy_from_slice(&0.2f32.to_bits().to_le_bytes());
|
|
let second_row_offset = first_row_offset + 0x22;
|
|
let second_name = b"Farm Corn";
|
|
payload[second_row_offset..second_row_offset + second_name.len()]
|
|
.copy_from_slice(second_name);
|
|
payload[second_row_offset + 0x1e..second_row_offset + 0x22]
|
|
.copy_from_slice(&0.45f32.to_bits().to_le_bytes());
|
|
|
|
let profile_probe = parse_save_region_profile_collection_probe(&payload)
|
|
.expect("profile collection probe should parse");
|
|
|
|
assert_eq!(profile_probe.direct_collection_flag, 1);
|
|
assert_eq!(profile_probe.entry_stride, 0x22);
|
|
assert_eq!(profile_probe.live_id_bound, 3);
|
|
assert_eq!(profile_probe.live_record_count, 2);
|
|
assert_eq!(profile_probe.entry_start_relative_offset, 0x20);
|
|
assert_eq!(profile_probe.entries.len(), 2);
|
|
assert_eq!(profile_probe.entries[0].name, "House");
|
|
assert_eq!(profile_probe.entries[0].trailing_weight_f32, 0.2);
|
|
assert_eq!(profile_probe.entries[1].name, "Farm Corn");
|
|
assert_eq!(profile_probe.entries[1].trailing_weight_f32, 0.45);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_region_queued_notice_record_probe_from_seeded_node() {
|
|
let mut bytes = vec![0u8; 0x200];
|
|
let node_base_offset = 0x80usize;
|
|
bytes[node_base_offset + 4..node_base_offset + 8]
|
|
.copy_from_slice(&SAVE_REGION_QUEUED_NOTICE_PAYLOAD_SEED.to_le_bytes());
|
|
bytes[node_base_offset + 8..node_base_offset + 12]
|
|
.copy_from_slice(&SAVE_REGION_QUEUED_NOTICE_NODE_KIND.to_le_bytes());
|
|
bytes[node_base_offset + 12..node_base_offset + 16].copy_from_slice(&0u32.to_le_bytes());
|
|
bytes[node_base_offset + 16..node_base_offset + 20].copy_from_slice(&5u32.to_le_bytes());
|
|
bytes[node_base_offset + 20..node_base_offset + 24].copy_from_slice(&1200u32.to_le_bytes());
|
|
bytes[node_base_offset + 24..node_base_offset + 28].copy_from_slice(&(-1i32).to_le_bytes());
|
|
bytes[node_base_offset + 28..node_base_offset + 32].copy_from_slice(&(-1i32).to_le_bytes());
|
|
|
|
let probe = parse_save_region_queued_notice_record_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
Some(&SmpSaveTaggedCollectionHeaderProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-region-tagged-header-counts".to_string(),
|
|
semantic_family: "scenario-save-region-header-counts".to_string(),
|
|
metadata_tag_offset: 0,
|
|
records_tag_offset: 0,
|
|
close_tag_offset: 0,
|
|
direct_collection_flag: 0,
|
|
direct_collection_flag_hex: "0x00000000".to_string(),
|
|
direct_record_stride: 0x06,
|
|
direct_record_stride_hex: "0x00000006".to_string(),
|
|
live_id_bound: 0x96,
|
|
live_id_bound_hex: "0x00000096".to_string(),
|
|
live_record_count: 0x91,
|
|
live_record_count_hex: "0x00000091".to_string(),
|
|
header_words: vec![],
|
|
header_hex_words: vec![],
|
|
evidence: vec![],
|
|
}),
|
|
)
|
|
.expect("region queued notice record probe should parse");
|
|
|
|
assert_eq!(probe.entries.len(), 1);
|
|
assert_eq!(probe.entries[0].node_base_offset, node_base_offset);
|
|
assert_eq!(probe.entries[0].payload_seed_dword_hex, "0x005c87a8");
|
|
assert_eq!(probe.entries[0].kind, SAVE_REGION_QUEUED_NOTICE_NODE_KIND);
|
|
assert_eq!(probe.entries[0].region_id, 5);
|
|
assert_eq!(probe.entries[0].amount, 1200);
|
|
assert_eq!(probe.entries[0].trailing_sentinel_i32_0, -1);
|
|
assert_eq!(probe.entries[0].trailing_sentinel_i32_1, -1);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_region_fixed_row_run_candidate_probe_from_seeded_rows() {
|
|
let mut bytes = vec![0u8; 0x200];
|
|
let count_offset = 0x20usize;
|
|
let rows_offset = count_offset + 4;
|
|
let metadata_tag_offset = 0x120usize;
|
|
bytes[count_offset..count_offset + 4].copy_from_slice(&2u32.to_le_bytes());
|
|
|
|
let first_row = rows_offset;
|
|
bytes[first_row..first_row + 4].copy_from_slice(&11u32.to_le_bytes());
|
|
bytes[first_row + 4..first_row + 8].copy_from_slice(&0.25f32.to_bits().to_le_bytes());
|
|
bytes[first_row + 8..first_row + 12].copy_from_slice(&0x11223344u32.to_le_bytes());
|
|
bytes[first_row + 0x28] = 0x07;
|
|
|
|
let second_row = rows_offset + SAVE_REGION_FIXED_ROW_STRIDE;
|
|
bytes[second_row..second_row + 4].copy_from_slice(&12u32.to_le_bytes());
|
|
bytes[second_row + 4..second_row + 8].copy_from_slice(&0.5f32.to_bits().to_le_bytes());
|
|
bytes[second_row + 8..second_row + 12].copy_from_slice(&0x55667788u32.to_le_bytes());
|
|
bytes[second_row + 0x28] = 0x08;
|
|
|
|
let probe = parse_save_region_fixed_row_run_candidate_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
Some(&SmpSaveTaggedCollectionHeaderProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-region-tagged-header-counts".to_string(),
|
|
semantic_family: "scenario-save-region-header-counts".to_string(),
|
|
metadata_tag_offset,
|
|
records_tag_offset: 0,
|
|
close_tag_offset: 0,
|
|
direct_collection_flag: 0,
|
|
direct_collection_flag_hex: "0x00000000".to_string(),
|
|
direct_record_stride: 0x06,
|
|
direct_record_stride_hex: "0x00000006".to_string(),
|
|
live_id_bound: 0x96,
|
|
live_id_bound_hex: "0x00000096".to_string(),
|
|
live_record_count: 2,
|
|
live_record_count_hex: "0x00000002".to_string(),
|
|
header_words: vec![],
|
|
header_hex_words: vec![],
|
|
evidence: vec![],
|
|
}),
|
|
)
|
|
.expect("region fixed-row run candidate probe should parse");
|
|
|
|
assert_eq!(probe.target_row_count, 2);
|
|
assert_eq!(probe.target_row_stride, SAVE_REGION_FIXED_ROW_STRIDE);
|
|
assert_eq!(probe.candidates.len(), 1);
|
|
assert_eq!(probe.candidates[0].count_offset, count_offset);
|
|
assert_eq!(probe.candidates[0].rows_offset, rows_offset);
|
|
assert_eq!(
|
|
probe.candidates[0].best_probable_density_lane_relative_offset_hex,
|
|
Some("0x4".to_string())
|
|
);
|
|
assert_eq!(
|
|
probe.candidates[0].dword_lane_summaries[0].small_unsigned_count,
|
|
2
|
|
);
|
|
assert_eq!(
|
|
probe.candidates[0].dword_lane_summaries[1].probable_normal_f32_count,
|
|
2
|
|
);
|
|
assert_eq!(probe.candidates[0].trailing_byte_nonzero_count, 2);
|
|
assert_eq!(
|
|
probe.candidates[0].trailing_byte_sample_values_hex,
|
|
vec!["0x07".to_string(), "0x08".to_string()]
|
|
);
|
|
assert_eq!(
|
|
probe.candidates[0].shape_signature,
|
|
"pf32=[0x4:2,0x8:2]|small=[0x0:2]|zero=[]|trail=0/2"
|
|
);
|
|
assert_eq!(
|
|
probe.candidates[0].shape_family_signature,
|
|
"dense_pf32=[0x4,0x8]|small_nonzero=[0x0,0x4,0x8]|partial_zero=[]|trail_bucket=0/0"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_placed_structure_record_triplet_probe_from_dual_name_rows() {
|
|
let mut bytes = vec![0u8; 0x400];
|
|
let metadata_tag_offset = 0x40usize;
|
|
let records_tag_offset = 0x140usize;
|
|
let close_tag_offset = 0x260usize;
|
|
bytes[metadata_tag_offset..metadata_tag_offset + 4]
|
|
.copy_from_slice(&0x000036b1u32.to_le_bytes());
|
|
bytes[records_tag_offset..records_tag_offset + 4]
|
|
.copy_from_slice(&0x000036b2u32.to_le_bytes());
|
|
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x000036b3u32.to_le_bytes());
|
|
let mut cursor = records_tag_offset + 4;
|
|
for (primary, secondary, lane0, lane1, lane2, lane3, lane4) in [
|
|
(
|
|
"StationA",
|
|
"StationSetA",
|
|
43111.92f32,
|
|
1385.5f32,
|
|
34581.95f32,
|
|
0.0f32,
|
|
5.9760494f32,
|
|
),
|
|
(
|
|
"StationB",
|
|
"StationSetB",
|
|
44000.0f32,
|
|
1200.0f32,
|
|
33000.0f32,
|
|
0.0f32,
|
|
4.5f32,
|
|
),
|
|
] {
|
|
bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_NAME_TAG.to_le_bytes());
|
|
bytes[cursor + 4] = primary.len() as u8;
|
|
bytes[cursor + 5..cursor + 5 + primary.len()].copy_from_slice(primary.as_bytes());
|
|
let second_len_offset = cursor + 5 + primary.len();
|
|
bytes[second_len_offset] = secondary.len() as u8;
|
|
bytes[second_len_offset + 1..second_len_offset + 1 + secondary.len()]
|
|
.copy_from_slice(secondary.as_bytes());
|
|
cursor += 0x19;
|
|
bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_POLICY_TAG.to_le_bytes());
|
|
bytes[cursor + 4..cursor + 8].copy_from_slice(&lane0.to_bits().to_le_bytes());
|
|
bytes[cursor + 8..cursor + 12].copy_from_slice(&lane1.to_bits().to_le_bytes());
|
|
bytes[cursor + 12..cursor + 16].copy_from_slice(&lane2.to_bits().to_le_bytes());
|
|
bytes[cursor + 16..cursor + 20].copy_from_slice(&lane3.to_bits().to_le_bytes());
|
|
bytes[cursor + 20..cursor + 24].copy_from_slice(&lane4.to_bits().to_le_bytes());
|
|
bytes[cursor + 28..cursor + 30].copy_from_slice(&0x0101u16.to_le_bytes());
|
|
cursor += 0x1e;
|
|
bytes[cursor..cursor + 2]
|
|
.copy_from_slice(&SAVE_REGION_RECORD_PROFILE_TAG.to_le_bytes());
|
|
bytes[cursor + 4..cursor + 8].copy_from_slice(&0x5dc1u32.to_le_bytes());
|
|
let mut payload_cursor = cursor + 8;
|
|
bytes[payload_cursor] = primary.len() as u8;
|
|
bytes[payload_cursor + 1..payload_cursor + 1 + primary.len()]
|
|
.copy_from_slice(primary.as_bytes());
|
|
payload_cursor += 1 + primary.len();
|
|
bytes[payload_cursor] = 0;
|
|
payload_cursor += 1;
|
|
bytes[payload_cursor] = secondary.len() as u8;
|
|
bytes[payload_cursor + 1..payload_cursor + 1 + secondary.len()]
|
|
.copy_from_slice(secondary.as_bytes());
|
|
payload_cursor += 1 + secondary.len();
|
|
bytes[payload_cursor] = 0;
|
|
payload_cursor += 1;
|
|
bytes[payload_cursor..payload_cursor + 4].copy_from_slice(&0x0e373500u32.to_le_bytes());
|
|
bytes[payload_cursor + 4..payload_cursor + 8].copy_from_slice(&(-1i32).to_le_bytes());
|
|
bytes[payload_cursor + 8..payload_cursor + 12]
|
|
.copy_from_slice(&0x5dc2u32.to_le_bytes());
|
|
cursor += 0x18 + primary.len() + secondary.len();
|
|
}
|
|
|
|
let header_probe = SmpSaveTaggedCollectionHeaderProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-placed-structure-tagged-header-counts".to_string(),
|
|
semantic_family: "scenario-save-placed-structure-header-counts".to_string(),
|
|
metadata_tag_offset,
|
|
records_tag_offset,
|
|
close_tag_offset,
|
|
direct_collection_flag: 0,
|
|
direct_collection_flag_hex: "0x00000000".to_string(),
|
|
direct_record_stride: 0x06,
|
|
direct_record_stride_hex: "0x00000006".to_string(),
|
|
live_id_bound: 3,
|
|
live_id_bound_hex: "0x00000003".to_string(),
|
|
live_record_count: 2,
|
|
live_record_count_hex: "0x00000002".to_string(),
|
|
header_words: vec![0, 6, 0x0a, 0x14, 3, 2],
|
|
header_hex_words: vec![],
|
|
evidence: vec![],
|
|
};
|
|
let triplet_probe =
|
|
parse_save_placed_structure_record_triplet_probe(&bytes, Some(&header_probe))
|
|
.expect("placed-structure triplet probe should parse");
|
|
|
|
assert_eq!(triplet_probe.record_count, 2);
|
|
assert_eq!(triplet_probe.entries[0].primary_name, "StationA");
|
|
assert_eq!(triplet_probe.entries[0].secondary_name, "StationSetA");
|
|
assert_eq!(triplet_probe.entries[0].policy_chunk_len, 0x1a);
|
|
assert_eq!(triplet_probe.entries[0].policy_f32_lane_4, 5.9760494);
|
|
assert_eq!(triplet_probe.entries[0].policy_trailing_word, 0x0101);
|
|
assert_eq!(
|
|
triplet_probe.entries[0].profile_open_marker_hex,
|
|
"0x00005dc1"
|
|
);
|
|
assert_eq!(
|
|
triplet_probe.entries[0].profile_repeated_primary_name,
|
|
"StationA"
|
|
);
|
|
assert_eq!(
|
|
triplet_probe.entries[0].profile_repeated_secondary_name,
|
|
"StationSetA"
|
|
);
|
|
assert_eq!(
|
|
triplet_probe.entries[0].profile_footer_relative_offset_hex,
|
|
"0x1b"
|
|
);
|
|
assert_eq!(triplet_probe.entries[0].profile_pre_footer_padding_len, 1);
|
|
assert_eq!(
|
|
triplet_probe.entries[0].profile_pre_footer_padding_hex_bytes,
|
|
vec!["0x00".to_string()]
|
|
);
|
|
assert_eq!(triplet_probe.entries[0].profile_companion_byte_u8, Some(0));
|
|
assert_eq!(
|
|
triplet_probe.entries[0]
|
|
.profile_companion_byte_hex
|
|
.as_deref(),
|
|
Some("0x00")
|
|
);
|
|
assert_eq!(
|
|
triplet_probe.entries[0].profile_payload_dword_hex,
|
|
"0x0e373500"
|
|
);
|
|
assert_eq!(triplet_probe.entries[0].profile_sentinel_i32, -1);
|
|
assert_eq!(triplet_probe.entries[0].profile_status_kind, "unset");
|
|
assert_eq!(triplet_probe.entries[0].farm_growth_stage_index, None);
|
|
assert_eq!(
|
|
triplet_probe.entries[0].profile_close_marker_hex,
|
|
"0x00005dc2"
|
|
);
|
|
assert_eq!(triplet_probe.entries[1].primary_name, "StationB");
|
|
assert_eq!(triplet_probe.entries[1].secondary_name, "StationSetB");
|
|
assert_eq!(triplet_probe.entries[1].policy_f32_lane_1, 1200.0);
|
|
}
|
|
|
|
#[test]
|
|
fn derives_placed_structure_farm_growth_stage_from_nonnegative_status() {
|
|
assert_eq!(
|
|
derive_save_placed_structure_profile_status("FarmCorn", "FarmSet", 4),
|
|
("farm_growth_stage_bucket", Some(4))
|
|
);
|
|
assert_eq!(
|
|
derive_save_placed_structure_profile_status("StationA", "StationSetA", -1),
|
|
("unset", None)
|
|
);
|
|
assert_eq!(
|
|
derive_save_placed_structure_profile_status("StationA", "StationSetA", 4),
|
|
("opaque_nondefault", None)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_placed_structure_dynamic_side_buffer_probe_from_embedded_name_row() {
|
|
let mut bytes = vec![0u8; 0x400];
|
|
let metadata_tag_offset = 0x40usize;
|
|
let records_tag_offset = 0x140usize;
|
|
let close_tag_offset = 0x220usize;
|
|
bytes[metadata_tag_offset..metadata_tag_offset + 4]
|
|
.copy_from_slice(&0x000038a5u32.to_le_bytes());
|
|
bytes[records_tag_offset..records_tag_offset + 4]
|
|
.copy_from_slice(&0x000038a6u32.to_le_bytes());
|
|
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x000038a7u32.to_le_bytes());
|
|
let header_words = [
|
|
0u32, 0x06, 1000, 500, 1000, 388, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
];
|
|
for (index, word) in header_words.into_iter().enumerate() {
|
|
let offset = metadata_tag_offset + 4 + index * 4;
|
|
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
|
|
}
|
|
let payload_offset = records_tag_offset + 4;
|
|
bytes[payload_offset..payload_offset + 4].copy_from_slice(&0x0005d368u32.to_le_bytes());
|
|
bytes[payload_offset + 4..payload_offset + 6].copy_from_slice(&0x0001u16.to_le_bytes());
|
|
bytes[payload_offset + 6] = 0xff;
|
|
let name_tag_offset = payload_offset + 7;
|
|
bytes[name_tag_offset..name_tag_offset + 2]
|
|
.copy_from_slice(&SAVE_REGION_RECORD_NAME_TAG.to_le_bytes());
|
|
let first_name = "TrackCapST_Cap.3dp";
|
|
let second_name = "Infrastructure";
|
|
bytes[name_tag_offset + 4] = first_name.len() as u8;
|
|
bytes[name_tag_offset + 5..name_tag_offset + 5 + first_name.len()]
|
|
.copy_from_slice(first_name.as_bytes());
|
|
let second_len_offset = name_tag_offset + 5 + first_name.len();
|
|
bytes[second_len_offset] = second_name.len() as u8;
|
|
bytes[second_len_offset + 1..second_len_offset + 1 + second_name.len()]
|
|
.copy_from_slice(second_name.as_bytes());
|
|
|
|
let probe = parse_save_placed_structure_dynamic_side_buffer_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
)
|
|
.expect("placed-structure dynamic side-buffer probe should parse");
|
|
|
|
assert_eq!(probe.direct_record_stride, 0x06);
|
|
assert_eq!(probe.live_id_bound, 1000);
|
|
assert_eq!(probe.live_record_count, 388);
|
|
assert_eq!(probe.owner_shared_dword_hex, "0x0005d368");
|
|
assert_eq!(probe.owner_shared_dword_relative_offset, 0);
|
|
assert!(probe.owner_shared_dword_matches_first_compact_prefix_leading_dword);
|
|
assert_eq!(probe.first_record_child_count_after_owner_shared, Some(1));
|
|
assert_eq!(
|
|
probe
|
|
.first_record_saved_primary_child_byte_after_owner_shared_hex
|
|
.as_deref(),
|
|
Some("0xff")
|
|
);
|
|
assert_eq!(
|
|
probe.first_record_first_name_tag_relative_offset_after_owner_shared,
|
|
Some(3)
|
|
);
|
|
assert_eq!(probe.prefix_leading_dword_hex, "0x0005d368");
|
|
assert_eq!(probe.prefix_trailing_word_hex, "0x0001");
|
|
assert_eq!(probe.prefix_separator_byte_hex, "0xff");
|
|
assert_eq!(probe.first_embedded_name_tag_relative_offset, 7);
|
|
assert_eq!(probe.embedded_name_tag_count, 1);
|
|
assert_eq!(probe.decoded_embedded_name_row_count, 1);
|
|
assert_eq!(probe.decoded_embedded_name_row_with_tertiary_name_count, 0);
|
|
assert_eq!(probe.unique_compact_prefix_pattern_count, 1);
|
|
assert_eq!(
|
|
probe.prefix_leading_dword_matching_embedded_profile_tag_count,
|
|
0
|
|
);
|
|
assert_eq!(probe.unique_embedded_name_pair_count, 1);
|
|
assert_eq!(
|
|
probe.first_embedded_primary_name.as_deref(),
|
|
Some("TrackCapST_Cap.3dp")
|
|
);
|
|
assert_eq!(
|
|
probe.first_embedded_secondary_name.as_deref(),
|
|
Some("Infrastructure")
|
|
);
|
|
assert_eq!(probe.first_embedded_tertiary_name.as_deref(), None);
|
|
assert_eq!(probe.compact_prefix_pattern_summaries.len(), 1);
|
|
assert_eq!(
|
|
probe.compact_prefix_pattern_summaries[0].prefix_leading_dword_hex,
|
|
"0x0005d368"
|
|
);
|
|
assert_eq!(probe.compact_prefix_pattern_summaries[0].count, 1);
|
|
assert_eq!(
|
|
probe.compact_prefix_pattern_summaries[0].cap_like_primary_name_count,
|
|
1
|
|
);
|
|
assert_eq!(
|
|
probe.compact_prefix_pattern_summaries[0].section_like_primary_name_count,
|
|
0
|
|
);
|
|
assert_eq!(probe.name_pair_summaries.len(), 1);
|
|
assert_eq!(probe.name_pair_summaries[0].count, 1);
|
|
assert_eq!(
|
|
probe.name_pair_summaries[0].dominant_prefix_leading_dword_hex,
|
|
"0x0005d368"
|
|
);
|
|
let payload_envelope_summary = probe
|
|
.payload_envelope_summary
|
|
.as_ref()
|
|
.expect("payload envelope summary should be present");
|
|
assert_eq!(
|
|
payload_envelope_summary.row_count_with_policy_tag_before_next_name,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
payload_envelope_summary.row_count_with_complete_0x55f1_0x55f2_0x55f3_envelope,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
payload_envelope_summary.row_count_missing_policy_tag_before_next_name,
|
|
1
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn summarizes_placed_structure_dynamic_side_buffer_compact_prefix_patterns() {
|
|
let mut bytes = vec![0u8; 0x600];
|
|
let metadata_tag_offset = 0x40usize;
|
|
let records_tag_offset = 0x140usize;
|
|
let close_tag_offset = 0x320usize;
|
|
bytes[metadata_tag_offset..metadata_tag_offset + 4]
|
|
.copy_from_slice(&0x000038a5u32.to_le_bytes());
|
|
bytes[records_tag_offset..records_tag_offset + 4]
|
|
.copy_from_slice(&0x000038a6u32.to_le_bytes());
|
|
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x000038a7u32.to_le_bytes());
|
|
let header_words = [
|
|
0u32, 0x06, 1000, 500, 1000, 388, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
];
|
|
for (index, word) in header_words.into_iter().enumerate() {
|
|
let offset = metadata_tag_offset + 4 + index * 4;
|
|
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
|
|
}
|
|
let mut cursor = records_tag_offset + 4;
|
|
for (leading_dword, primary_name) in [
|
|
(0x000055f3u32, "TunnelSTBrick_Section.3dp"),
|
|
(0x000055f3u32, "TunnelSTBrick_Cap.3dp"),
|
|
(0xff0000ffu32, "TunnelSTBrick_Cap.3dp"),
|
|
] {
|
|
bytes[cursor..cursor + 4].copy_from_slice(&leading_dword.to_le_bytes());
|
|
bytes[cursor + 4..cursor + 6].copy_from_slice(&0x0001u16.to_le_bytes());
|
|
bytes[cursor + 6] = 0xff;
|
|
let name_tag_offset = cursor + 7;
|
|
bytes[name_tag_offset..name_tag_offset + 2]
|
|
.copy_from_slice(&SAVE_REGION_RECORD_NAME_TAG.to_le_bytes());
|
|
let secondary_name = "Infrastructure";
|
|
bytes[name_tag_offset + 4] = primary_name.len() as u8;
|
|
bytes[name_tag_offset + 5..name_tag_offset + 5 + primary_name.len()]
|
|
.copy_from_slice(primary_name.as_bytes());
|
|
let second_len_offset = name_tag_offset + 5 + primary_name.len();
|
|
bytes[second_len_offset] = secondary_name.len() as u8;
|
|
bytes[second_len_offset + 1..second_len_offset + 1 + secondary_name.len()]
|
|
.copy_from_slice(secondary_name.as_bytes());
|
|
cursor = second_len_offset + 1 + secondary_name.len();
|
|
}
|
|
|
|
let probe = parse_save_placed_structure_dynamic_side_buffer_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
)
|
|
.expect("placed-structure dynamic side-buffer probe should parse");
|
|
|
|
assert_eq!(probe.embedded_name_tag_count, 3);
|
|
assert_eq!(probe.decoded_embedded_name_row_count, 3);
|
|
assert_eq!(probe.decoded_embedded_name_row_with_tertiary_name_count, 0);
|
|
assert_eq!(probe.unique_compact_prefix_pattern_count, 2);
|
|
assert_eq!(
|
|
probe.prefix_leading_dword_matching_embedded_profile_tag_count,
|
|
2
|
|
);
|
|
assert_eq!(probe.unique_embedded_name_pair_count, 2);
|
|
assert_eq!(probe.compact_prefix_pattern_summaries.len(), 2);
|
|
assert_eq!(
|
|
probe.compact_prefix_pattern_summaries[0].prefix_leading_dword_hex,
|
|
"0x000055f3"
|
|
);
|
|
assert_eq!(probe.compact_prefix_pattern_summaries[0].count, 2);
|
|
assert_eq!(
|
|
probe.compact_prefix_pattern_summaries[0].section_like_primary_name_count,
|
|
1
|
|
);
|
|
assert_eq!(
|
|
probe.compact_prefix_pattern_summaries[0].cap_like_primary_name_count,
|
|
1
|
|
);
|
|
assert!(
|
|
probe.compact_prefix_pattern_summaries[0]
|
|
.prefix_leading_dword_matches_embedded_profile_tag
|
|
);
|
|
assert_eq!(
|
|
probe.compact_prefix_pattern_summaries[1].prefix_leading_dword_hex,
|
|
"0xff0000ff"
|
|
);
|
|
assert_eq!(probe.compact_prefix_pattern_summaries[1].count, 1);
|
|
assert_eq!(probe.name_pair_summaries.len(), 2);
|
|
assert_eq!(probe.name_pair_summaries[0].count, 2);
|
|
assert_eq!(
|
|
probe.name_pair_summaries[0].dominant_prefix_leading_dword_hex,
|
|
"0x000055f3"
|
|
);
|
|
assert_eq!(probe.name_pair_summaries[1].count, 1);
|
|
let payload_envelope_summary = probe
|
|
.payload_envelope_summary
|
|
.as_ref()
|
|
.expect("payload envelope summary should be present");
|
|
assert_eq!(
|
|
payload_envelope_summary.row_count_with_policy_tag_before_next_name,
|
|
0
|
|
);
|
|
assert_eq!(
|
|
payload_envelope_summary.row_count_missing_policy_tag_before_next_name,
|
|
3
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_save_len_prefixed_ascii_name_triplet_with_optional_third_name() {
|
|
let bytes = [
|
|
5u8, b'F', b'i', b'r', b's', b't', 0, 6, b'S', b'e', b'c', b'o', b'n', b'd', 0, 5,
|
|
b'T', b'h', b'i', b'r', b'd',
|
|
];
|
|
let parsed = parse_save_len_prefixed_ascii_name_triplet(&bytes)
|
|
.expect("triplet parser should decode three len-prefixed ascii names");
|
|
assert_eq!(parsed.0, "First");
|
|
assert_eq!(parsed.1, "Second");
|
|
assert_eq!(parsed.2.as_deref(), Some("Third"));
|
|
}
|
|
|
|
#[test]
|
|
fn parses_save_len_prefixed_ascii_name_triplet_with_extended_length_prefix() {
|
|
let mut bytes = Vec::new();
|
|
bytes.extend_from_slice(&[0x80, 0x03, b'A', b'B', b'C']);
|
|
bytes.extend_from_slice(&[0, 1, b'X']);
|
|
let parsed = parse_save_len_prefixed_ascii_name_triplet(&bytes)
|
|
.expect("triplet parser should decode extended-length prefix");
|
|
assert_eq!(parsed.0, "ABC");
|
|
assert_eq!(parsed.1, "X");
|
|
assert_eq!(parsed.2, None);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_save_len_prefixed_ascii_name_triplet_with_consumed_len() {
|
|
let bytes = [
|
|
5u8, b'F', b'i', b'r', b's', b't', 0, 6, b'S', b'e', b'c', b'o', b'n', b'd', 0, 5,
|
|
b'T', b'h', b'i', b'r', b'd', 0xff,
|
|
];
|
|
let (parsed, consumed_len) =
|
|
parse_save_len_prefixed_ascii_name_triplet_and_consumed_len(&bytes)
|
|
.expect("triplet parser should decode consumed len");
|
|
assert_eq!(parsed.0, "First");
|
|
assert_eq!(parsed.1, "Second");
|
|
assert_eq!(parsed.2.as_deref(), Some("Third"));
|
|
assert_eq!(consumed_len, 21);
|
|
}
|
|
|
|
#[test]
|
|
fn aligns_placed_structure_dynamic_side_buffer_name_pairs_with_triplets() {
|
|
let side_buffer = SmpSavePlacedStructureDynamicSideBufferProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-placed-structure-dynamic-side-buffer-records".to_string(),
|
|
semantic_family: "scenario-save-placed-structure-dynamic-side-buffer-records"
|
|
.to_string(),
|
|
metadata_tag_offset: 0,
|
|
records_tag_offset: 0,
|
|
close_tag_offset: 0,
|
|
records_span_len: 0,
|
|
direct_record_stride: 6,
|
|
direct_record_stride_hex: "0x00000006".to_string(),
|
|
live_id_bound: 1000,
|
|
live_id_bound_hex: "0x000003e8".to_string(),
|
|
live_record_count: 10,
|
|
live_record_count_hex: "0x0000000a".to_string(),
|
|
owner_shared_dword: 0,
|
|
owner_shared_dword_hex: "0x00000000".to_string(),
|
|
owner_shared_dword_relative_offset: 0,
|
|
owner_shared_dword_matches_first_compact_prefix_leading_dword: true,
|
|
first_record_child_count_after_owner_shared: None,
|
|
first_record_child_count_after_owner_shared_hex: None,
|
|
first_record_saved_primary_child_byte_after_owner_shared: None,
|
|
first_record_saved_primary_child_byte_after_owner_shared_hex: None,
|
|
first_record_first_name_tag_relative_offset_after_owner_shared: None,
|
|
prefix_leading_dword: 0,
|
|
prefix_leading_dword_hex: "0x00000000".to_string(),
|
|
prefix_trailing_word: 1,
|
|
prefix_trailing_word_hex: "0x0001".to_string(),
|
|
prefix_separator_byte: 0xff,
|
|
prefix_separator_byte_hex: "0xff".to_string(),
|
|
first_embedded_name_tag_relative_offset: 7,
|
|
embedded_name_tag_count: 3,
|
|
decoded_embedded_name_row_count: 3,
|
|
decoded_embedded_name_row_with_tertiary_name_count: 0,
|
|
unique_compact_prefix_pattern_count: 2,
|
|
prefix_leading_dword_matching_embedded_profile_tag_count: 2,
|
|
unique_embedded_name_pair_count: 2,
|
|
first_embedded_primary_name: Some("TunnelSTBrick_Section.3dp".to_string()),
|
|
first_embedded_secondary_name: Some("Infrastructure".to_string()),
|
|
first_embedded_tertiary_name: None,
|
|
embedded_name_row_samples: vec![],
|
|
compact_prefix_pattern_summaries: vec![],
|
|
name_pair_summaries: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePairSummary {
|
|
primary_name: "TunnelSTBrick_Section.3dp".to_string(),
|
|
secondary_name: "Infrastructure".to_string(),
|
|
count: 2,
|
|
first_name_tag_relative_offset: 7,
|
|
unique_compact_prefix_pattern_count: 1,
|
|
dominant_prefix_leading_dword: 0x55f3,
|
|
dominant_prefix_leading_dword_hex: "0x000055f3".to_string(),
|
|
dominant_prefix_trailing_word: 1,
|
|
dominant_prefix_trailing_word_hex: "0x0001".to_string(),
|
|
dominant_prefix_separator_byte: 0xff,
|
|
dominant_prefix_separator_byte_hex: "0xff".to_string(),
|
|
dominant_prefix_count: 2,
|
|
},
|
|
SmpSavePlacedStructureDynamicSideBufferNamePairSummary {
|
|
primary_name: "BridgeSTWood_Section.3dp".to_string(),
|
|
secondary_name: "Infrastructure".to_string(),
|
|
count: 1,
|
|
first_name_tag_relative_offset: 27,
|
|
unique_compact_prefix_pattern_count: 1,
|
|
dominant_prefix_leading_dword: 0xff000000,
|
|
dominant_prefix_leading_dword_hex: "0xff000000".to_string(),
|
|
dominant_prefix_trailing_word: 1,
|
|
dominant_prefix_trailing_word_hex: "0x0001".to_string(),
|
|
dominant_prefix_separator_byte: 0xff,
|
|
dominant_prefix_separator_byte_hex: "0xff".to_string(),
|
|
dominant_prefix_count: 1,
|
|
},
|
|
],
|
|
payload_envelope_summary: None,
|
|
live_entry_prelude_summary: None,
|
|
evidence: vec![],
|
|
};
|
|
let triplets = SmpSavePlacedStructureRecordTripletProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-placed-structure-record-triplets".to_string(),
|
|
semantic_family: "scenario-save-placed-structure-record-triplets".to_string(),
|
|
records_tag_offset: 0,
|
|
close_tag_offset: 0,
|
|
record_count: 2,
|
|
entries: vec![
|
|
SmpSavePlacedStructureRecordTripletEntryProbe {
|
|
record_index: 0,
|
|
primary_name: "TunnelSTBrick_Section.3dp".to_string(),
|
|
secondary_name: "Infrastructure".to_string(),
|
|
name_tag_relative_offset: 0,
|
|
policy_tag_relative_offset: 0,
|
|
profile_tag_relative_offset: 0,
|
|
policy_chunk_len: 0,
|
|
profile_chunk_len: 0,
|
|
policy_f32_lane_0: 0.0,
|
|
policy_f32_lane_1: 0.0,
|
|
policy_f32_lane_2: 0.0,
|
|
policy_f32_lane_3: 0.0,
|
|
policy_f32_lane_4: 0.0,
|
|
policy_reserved_dword: 0,
|
|
policy_trailing_word: 0,
|
|
policy_trailing_word_hex: "0x0000".to_string(),
|
|
profile_open_marker: 0,
|
|
profile_open_marker_hex: "0x00000000".to_string(),
|
|
profile_repeated_primary_name: "TunnelSTBrick_Section.3dp".to_string(),
|
|
profile_repeated_secondary_name: "Infrastructure".to_string(),
|
|
profile_footer_relative_offset: 0,
|
|
profile_footer_relative_offset_hex: "0x0".to_string(),
|
|
profile_pre_footer_padding_len: 0,
|
|
profile_pre_footer_padding_hex_bytes: Vec::new(),
|
|
profile_companion_byte_u8: None,
|
|
profile_companion_byte_hex: None,
|
|
profile_payload_dword: 0,
|
|
profile_payload_dword_hex: "0x00000000".to_string(),
|
|
profile_sentinel_i32: -1,
|
|
profile_status_kind: "unset".to_string(),
|
|
farm_growth_stage_index: None,
|
|
profile_close_marker: 0,
|
|
profile_close_marker_hex: "0x00000000".to_string(),
|
|
},
|
|
SmpSavePlacedStructureRecordTripletEntryProbe {
|
|
record_index: 1,
|
|
primary_name: "TrackCapST_Cap.3dp".to_string(),
|
|
secondary_name: "Infrastructure".to_string(),
|
|
name_tag_relative_offset: 0,
|
|
policy_tag_relative_offset: 0,
|
|
profile_tag_relative_offset: 0,
|
|
policy_chunk_len: 0,
|
|
profile_chunk_len: 0,
|
|
policy_f32_lane_0: 0.0,
|
|
policy_f32_lane_1: 0.0,
|
|
policy_f32_lane_2: 0.0,
|
|
policy_f32_lane_3: 0.0,
|
|
policy_f32_lane_4: 0.0,
|
|
policy_reserved_dword: 0,
|
|
policy_trailing_word: 0,
|
|
policy_trailing_word_hex: "0x0000".to_string(),
|
|
profile_open_marker: 0,
|
|
profile_open_marker_hex: "0x00000000".to_string(),
|
|
profile_repeated_primary_name: "TrackCapST_Cap.3dp".to_string(),
|
|
profile_repeated_secondary_name: "Infrastructure".to_string(),
|
|
profile_footer_relative_offset: 0,
|
|
profile_footer_relative_offset_hex: "0x0".to_string(),
|
|
profile_pre_footer_padding_len: 0,
|
|
profile_pre_footer_padding_hex_bytes: Vec::new(),
|
|
profile_companion_byte_u8: None,
|
|
profile_companion_byte_hex: None,
|
|
profile_payload_dword: 0,
|
|
profile_payload_dword_hex: "0x00000000".to_string(),
|
|
profile_sentinel_i32: -1,
|
|
profile_status_kind: "unset".to_string(),
|
|
farm_growth_stage_index: None,
|
|
profile_close_marker: 0,
|
|
profile_close_marker_hex: "0x00000000".to_string(),
|
|
},
|
|
],
|
|
evidence: vec![],
|
|
};
|
|
|
|
let alignment =
|
|
summarize_placed_structure_dynamic_side_buffer_alignment(&side_buffer, &triplets);
|
|
|
|
assert_eq!(alignment.unique_side_buffer_name_pair_count, 2);
|
|
assert_eq!(alignment.unique_triplet_name_pair_count, 2);
|
|
assert_eq!(alignment.overlapping_name_pair_count, 1);
|
|
assert_eq!(
|
|
alignment.side_buffer_rows_with_matching_triplet_name_pair_count,
|
|
2
|
|
);
|
|
assert_eq!(
|
|
alignment.side_buffer_rows_without_matching_triplet_name_pair_count,
|
|
1
|
|
);
|
|
assert_eq!(
|
|
alignment.triplet_name_pairs_without_side_buffer_match_count,
|
|
1
|
|
);
|
|
assert_eq!(alignment.matched_name_pair_samples.len(), 1);
|
|
assert_eq!(alignment.unmatched_side_buffer_name_pair_samples.len(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_placed_structure_tagged_collection_header_probe_from_exact_u32_tags() {
|
|
let mut bytes = vec![0u8; 0x400];
|
|
let metadata_tag_offset = 0x40usize;
|
|
let records_tag_offset = 0x140usize;
|
|
let close_tag_offset = 0x180usize;
|
|
bytes[metadata_tag_offset..metadata_tag_offset + 4]
|
|
.copy_from_slice(&0x000036b1u32.to_le_bytes());
|
|
bytes[records_tag_offset..records_tag_offset + 4]
|
|
.copy_from_slice(&0x000036b2u32.to_le_bytes());
|
|
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x000036b3u32.to_le_bytes());
|
|
let header_words = [
|
|
0u32, 0x06, 0x0a, 0x14, 0x7ee, 0x7ea, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
];
|
|
for (index, word) in header_words.into_iter().enumerate() {
|
|
let offset = metadata_tag_offset + 4 + index * 4;
|
|
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
|
|
}
|
|
|
|
let probe = parse_save_placed_structure_collection_header_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
)
|
|
.expect("placed structure header probe should parse");
|
|
|
|
assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
|
|
assert_eq!(probe.records_tag_offset, records_tag_offset);
|
|
assert_eq!(probe.close_tag_offset, close_tag_offset);
|
|
assert_eq!(probe.direct_collection_flag, 0);
|
|
assert_eq!(probe.direct_record_stride, 0x06);
|
|
assert_eq!(probe.live_id_bound, 0x7ee);
|
|
assert_eq!(probe.live_record_count, 0x7ea);
|
|
}
|
|
|
|
#[test]
|
|
fn scans_unclassified_tagged_collection_header_probe_from_adjacent_low_tags() {
|
|
let mut bytes = vec![0u8; 0x400];
|
|
let metadata_tag_offset = 0x40usize;
|
|
let records_tag_offset = 0x140usize;
|
|
let close_tag_offset = 0x1c0usize;
|
|
bytes[metadata_tag_offset..metadata_tag_offset + 4]
|
|
.copy_from_slice(&0x00007001u32.to_le_bytes());
|
|
bytes[records_tag_offset..records_tag_offset + 4]
|
|
.copy_from_slice(&0x00007002u32.to_le_bytes());
|
|
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x00007003u32.to_le_bytes());
|
|
let header_words = [
|
|
0u32, 0x12, 0x0a, 0x14, 0x90, 0x78, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
|
];
|
|
for (index, word) in header_words.into_iter().enumerate() {
|
|
let offset = metadata_tag_offset + 4 + index * 4;
|
|
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
|
|
}
|
|
|
|
let probes = scan_save_unclassified_tagged_collection_header_probes(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
);
|
|
|
|
let probe = probes
|
|
.iter()
|
|
.find(|probe| probe.metadata_tag == 0x7001)
|
|
.expect("should include synthetic unclassified tag family");
|
|
assert_eq!(probe.records_tag, 0x7002);
|
|
assert_eq!(probe.close_tag, 0x7003);
|
|
assert_eq!(probe.direct_record_stride, 0x12);
|
|
assert_eq!(probe.live_id_bound, 0x90);
|
|
assert_eq!(probe.live_record_count, 0x78);
|
|
assert_eq!(
|
|
probe.records_span_len,
|
|
close_tag_offset - (records_tag_offset + 4)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_save_company_roster_probe_from_direct_records() {
|
|
let metadata_tag_offset = 0x40usize;
|
|
let stride = 0x7684usize;
|
|
let count = 2usize;
|
|
let start_offset = 0xc6usize;
|
|
let total_len = metadata_tag_offset + 4 + start_offset + stride * count + 0x400;
|
|
let mut bytes = vec![0u8; total_len];
|
|
bytes[metadata_tag_offset..metadata_tag_offset + 4]
|
|
.copy_from_slice(&0x000061a9u32.to_le_bytes());
|
|
let header_words = [
|
|
1u32,
|
|
0x7684,
|
|
5,
|
|
5,
|
|
5,
|
|
count as u32,
|
|
1,
|
|
1,
|
|
0,
|
|
0,
|
|
1,
|
|
1,
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
];
|
|
for (index, word) in header_words.into_iter().enumerate() {
|
|
let offset = metadata_tag_offset + 4 + index * 4;
|
|
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
|
|
}
|
|
let records_tag_offset = metadata_tag_offset + 4 + 0x200;
|
|
let close_tag_offset = records_tag_offset + 4;
|
|
bytes[records_tag_offset..records_tag_offset + 4]
|
|
.copy_from_slice(&0x000061aau32.to_le_bytes());
|
|
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x000061abu32.to_le_bytes());
|
|
|
|
for (
|
|
index,
|
|
(
|
|
name,
|
|
linked,
|
|
merger,
|
|
takeover,
|
|
bond_count,
|
|
_debt,
|
|
track_capacity,
|
|
mutable_support_scalar_raw_u32,
|
|
young_company_support_scalar_raw_u32,
|
|
support_progress_word,
|
|
recent_per_share_subscore_raw_u32,
|
|
cached_share_price_raw_u32,
|
|
chairman_salary_baseline,
|
|
chairman_salary_current,
|
|
chairman_bonus_year,
|
|
chairman_bonus_amount,
|
|
founding_year,
|
|
last_bankruptcy_year,
|
|
last_dividend_year,
|
|
current_issue_calendar_word,
|
|
current_issue_calendar_word_2,
|
|
prior_issue_calendar_word,
|
|
prior_issue_calendar_word_2,
|
|
preferred_locomotive_engine_type_raw_u8,
|
|
city_connection_latch,
|
|
linked_transit_latch,
|
|
linked_transit_route_anchor_entry_id,
|
|
linked_transit_route_anchor_fallback_counts,
|
|
),
|
|
) in [
|
|
(
|
|
"Company One",
|
|
1u32,
|
|
1862u32,
|
|
1865u32,
|
|
2u8,
|
|
1_000_000u32,
|
|
Some(603i32),
|
|
0x3f800000u32,
|
|
0x42340000u32,
|
|
17u32,
|
|
0x41f00000u32,
|
|
0x426c0000u32,
|
|
24u32,
|
|
31u32,
|
|
1849u32,
|
|
1250i32,
|
|
1842u32,
|
|
1851u32,
|
|
1848u32,
|
|
7u32,
|
|
8u32,
|
|
6u32,
|
|
7u32,
|
|
2u8,
|
|
true,
|
|
false,
|
|
Some(77u32),
|
|
[3u32, 5u32, 8u32],
|
|
),
|
|
(
|
|
"Company Two",
|
|
2u32,
|
|
0u32,
|
|
1871u32,
|
|
1u8,
|
|
500_000u32,
|
|
None,
|
|
0x40000000u32,
|
|
0x42700000u32,
|
|
33u32,
|
|
0x42000000u32,
|
|
0x42780000u32,
|
|
28u32,
|
|
36u32,
|
|
0u32,
|
|
0i32,
|
|
1845u32,
|
|
0u32,
|
|
1850u32,
|
|
3u32,
|
|
4u32,
|
|
2u32,
|
|
3u32,
|
|
1u8,
|
|
false,
|
|
true,
|
|
Some(41u32),
|
|
[13u32, 21u32, 34u32],
|
|
),
|
|
]
|
|
.into_iter()
|
|
.enumerate()
|
|
{
|
|
let record_offset = metadata_tag_offset + 4 + start_offset + index * stride;
|
|
bytes[record_offset..record_offset + 4]
|
|
.copy_from_slice(&((index + 1) as u32).to_le_bytes());
|
|
bytes[record_offset + 4..record_offset + 4 + name.len()]
|
|
.copy_from_slice(name.as_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_LINKED_CHAIRMAN_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_LINKED_CHAIRMAN_OFFSET + 4]
|
|
.copy_from_slice(&linked.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_ACTIVE_OFFSET] = 1;
|
|
bytes[record_offset + 0x47..record_offset + 0x4b]
|
|
.copy_from_slice(&20000u32.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_SUPPORT_SCALAR_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_SUPPORT_SCALAR_OFFSET + 4]
|
|
.copy_from_slice(&mutable_support_scalar_raw_u32.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_COMPANY_VALUE_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_COMPANY_VALUE_OFFSET + 4]
|
|
.copy_from_slice(&young_company_support_scalar_raw_u32.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_BOND_COUNT_OFFSET] = bond_count;
|
|
for slot_index in 0..bond_count as usize {
|
|
let slot_offset = record_offset
|
|
+ SAVE_COMPANY_RECORD_BOND_TABLE_OFFSET
|
|
+ slot_index * SAVE_COMPANY_RECORD_BOND_SLOT_STRIDE;
|
|
let (principal, coupon_rate) = if index == 0 && slot_index == 0 {
|
|
(900_000i32, 0.08f32)
|
|
} else if index == 0 && slot_index == 1 {
|
|
(650_000i32, 0.12f32)
|
|
} else {
|
|
(500_000i32, 0.10f32)
|
|
};
|
|
bytes[slot_offset..slot_offset + 4].copy_from_slice(&principal.to_le_bytes());
|
|
bytes[slot_offset + 4..slot_offset + 8]
|
|
.copy_from_slice(&(1894u32 + slot_index as u32).to_le_bytes());
|
|
bytes[slot_offset + 8..slot_offset + 12]
|
|
.copy_from_slice(&coupon_rate.to_le_bytes());
|
|
}
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_MERGER_COOLDOWN_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_MERGER_COOLDOWN_OFFSET + 4]
|
|
.copy_from_slice(&merger.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_TAKEOVER_COOLDOWN_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_TAKEOVER_COOLDOWN_OFFSET + 4]
|
|
.copy_from_slice(&takeover.to_le_bytes());
|
|
let raw_capacity = track_capacity.unwrap_or(-1);
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_TRACK_LAYING_CAPACITY_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_TRACK_LAYING_CAPACITY_OFFSET + 4]
|
|
.copy_from_slice(&raw_capacity.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_SUPPORT_PROGRESS_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_SUPPORT_PROGRESS_OFFSET + 4]
|
|
.copy_from_slice(&support_progress_word.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_RECENT_PER_SHARE_SUBSCORE_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_RECENT_PER_SHARE_SUBSCORE_OFFSET + 4]
|
|
.copy_from_slice(&recent_per_share_subscore_raw_u32.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_CACHED_SHARE_PRICE_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_CACHED_SHARE_PRICE_OFFSET + 4]
|
|
.copy_from_slice(&cached_share_price_raw_u32.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_BASELINE_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_BASELINE_OFFSET + 4]
|
|
.copy_from_slice(&chairman_salary_baseline.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_CURRENT_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_SALARY_CURRENT_OFFSET + 4]
|
|
.copy_from_slice(&chairman_salary_current.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_YEAR_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_YEAR_OFFSET + 4]
|
|
.copy_from_slice(&chairman_bonus_year.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_AMOUNT_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_CHAIRMAN_BONUS_AMOUNT_OFFSET + 4]
|
|
.copy_from_slice(&chairman_bonus_amount.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_FOUNDING_YEAR_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_FOUNDING_YEAR_OFFSET + 4]
|
|
.copy_from_slice(&founding_year.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_LAST_BANKRUPTCY_YEAR_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_LAST_BANKRUPTCY_YEAR_OFFSET + 4]
|
|
.copy_from_slice(&last_bankruptcy_year.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_LAST_DIVIDEND_YEAR_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_LAST_DIVIDEND_YEAR_OFFSET + 4]
|
|
.copy_from_slice(&last_dividend_year.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET + 4]
|
|
.copy_from_slice(¤t_issue_calendar_word.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET_2
|
|
..record_offset + SAVE_COMPANY_RECORD_CURRENT_ISSUE_CALENDAR_OFFSET_2 + 4]
|
|
.copy_from_slice(¤t_issue_calendar_word_2.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET
|
|
..record_offset + SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET + 4]
|
|
.copy_from_slice(&prior_issue_calendar_word.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET_2
|
|
..record_offset + SAVE_COMPANY_RECORD_PRIOR_ISSUE_CALENDAR_OFFSET_2 + 4]
|
|
.copy_from_slice(&prior_issue_calendar_word_2.to_le_bytes());
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_PREFERRED_LOCOMOTIVE_ENGINE_TYPE_OFFSET] =
|
|
preferred_locomotive_engine_type_raw_u8;
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_CITY_CONNECTION_LATCH_OFFSET] =
|
|
u8::from(city_connection_latch);
|
|
bytes[record_offset + SAVE_COMPANY_RECORD_LINKED_TRANSIT_LATCH_OFFSET] =
|
|
u8::from(linked_transit_latch);
|
|
if let Some(anchor_entry_id) = linked_transit_route_anchor_entry_id {
|
|
bytes[record_offset
|
|
+ SAVE_COMPANY_RECORD_LINKED_TRANSIT_ROUTE_ANCHOR_ENTRY_ID_OFFSET
|
|
..record_offset
|
|
+ SAVE_COMPANY_RECORD_LINKED_TRANSIT_ROUTE_ANCHOR_ENTRY_ID_OFFSET
|
|
+ 4]
|
|
.copy_from_slice(&anchor_entry_id.to_le_bytes());
|
|
}
|
|
for (fallback_index, relative_offset) in
|
|
SAVE_COMPANY_RECORD_LINKED_TRANSIT_ROUTE_ANCHOR_FALLBACK_COUNT_OFFSETS
|
|
.into_iter()
|
|
.enumerate()
|
|
{
|
|
bytes[record_offset + relative_offset..record_offset + relative_offset + 4]
|
|
.copy_from_slice(
|
|
&linked_transit_route_anchor_fallback_counts[fallback_index].to_le_bytes(),
|
|
);
|
|
}
|
|
let current_cash: f64 = if index == 0 { 125_000.0 } else { -25_000.0 };
|
|
let current_cash_slot_offset = record_offset
|
|
+ SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET
|
|
+ (crate::RUNTIME_COMPANY_STAT_SLOT_CURRENT_CASH as usize
|
|
* crate::RUNTIME_COMPANY_YEAR_STAT_FAMILY_SPAN as usize
|
|
* 8);
|
|
bytes[current_cash_slot_offset..current_cash_slot_offset + 8]
|
|
.copy_from_slice(¤t_cash.to_bits().to_le_bytes());
|
|
}
|
|
|
|
let header_probe = parse_save_company_collection_header_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
)
|
|
.expect("company header probe should parse");
|
|
let roster = parse_save_company_roster_probe(
|
|
&bytes,
|
|
Some(&header_probe),
|
|
Some(&SmpSaveWorldSelectionContextProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-direct-world-block".to_string(),
|
|
semantic_family: "scenario-selected-company-and-chairman-context".to_string(),
|
|
chunk_tag_offset: 0,
|
|
payload_offset: 0,
|
|
payload_len: 0,
|
|
payload_len_hex: "0x0".to_string(),
|
|
selected_company_id_offset: 0,
|
|
selected_company_id: 2,
|
|
selected_company_id_hex: "0x00000002".to_string(),
|
|
selected_chairman_profile_id_offset: 0,
|
|
selected_chairman_profile_id: 1,
|
|
selected_chairman_profile_id_hex: "0x00000001".to_string(),
|
|
chairman_slot_selector_offset: 0,
|
|
chairman_slot_selectors: vec![],
|
|
campaign_override_flag_offset: 0,
|
|
campaign_override_flag: 0,
|
|
campaign_override_flag_hex: "0x00".to_string(),
|
|
chairman_role_gate_offset: 0,
|
|
chairman_role_gate_bytes: vec![],
|
|
evidence: vec![],
|
|
}),
|
|
)
|
|
.expect("company roster should parse");
|
|
|
|
assert_eq!(roster.observed_entry_count, 2);
|
|
assert_eq!(roster.selected_company_id, Some(2));
|
|
assert_eq!(roster.entries.len(), 2);
|
|
assert_eq!(roster.entries[0].company_id, 1);
|
|
assert_eq!(roster.entries[0].current_cash, 125_000);
|
|
assert_eq!(roster.entries[0].linked_chairman_profile_id, Some(1));
|
|
assert_eq!(roster.entries[0].debt, 1_550_000);
|
|
assert_eq!(roster.entries[0].available_track_laying_capacity, Some(603));
|
|
assert_eq!(roster.entries[0].merger_cooldown_year, Some(1862));
|
|
let market_state = roster.entries[0]
|
|
.market_state
|
|
.as_ref()
|
|
.expect("company market state should load");
|
|
assert_eq!(market_state.outstanding_shares, 20_000);
|
|
assert_eq!(market_state.live_bond_slots.len(), 2);
|
|
assert_eq!(market_state.live_bond_slots[0].principal, 900_000);
|
|
assert_eq!(market_state.live_bond_slots[0].maturity_year, 1894);
|
|
assert_eq!(
|
|
market_state.live_bond_slots[1].coupon_rate_raw_u32,
|
|
0.12f32.to_bits()
|
|
);
|
|
assert_eq!(market_state.largest_live_bond_principal, Some(900_000));
|
|
assert_eq!(
|
|
market_state.highest_coupon_live_bond_principal,
|
|
Some(650_000)
|
|
);
|
|
assert_eq!(market_state.mutable_support_scalar_raw_u32, 0x3f800000);
|
|
assert_eq!(
|
|
market_state.young_company_support_scalar_raw_u32,
|
|
0x42340000
|
|
);
|
|
assert_eq!(market_state.support_progress_word, 17);
|
|
assert_eq!(market_state.recent_per_share_subscore_raw_u32, 0x41f00000);
|
|
assert_eq!(market_state.cached_share_price_raw_u32, 0x426c0000);
|
|
assert_eq!(market_state.chairman_salary_baseline, 24);
|
|
assert_eq!(market_state.chairman_salary_current, 31);
|
|
assert_eq!(market_state.chairman_bonus_year, 1849);
|
|
assert_eq!(market_state.chairman_bonus_amount, 1250);
|
|
assert_eq!(market_state.founding_year, 1842);
|
|
assert_eq!(market_state.last_bankruptcy_year, 1851);
|
|
assert_eq!(market_state.last_dividend_year, 1848);
|
|
assert_eq!(market_state.current_issue_calendar_word, 7);
|
|
assert_eq!(market_state.current_issue_calendar_word_2, 8);
|
|
assert_eq!(market_state.prior_issue_calendar_word, 6);
|
|
assert_eq!(market_state.prior_issue_calendar_word_2, 7);
|
|
assert_eq!(market_state.linked_transit_route_anchor_entry_id, Some(77));
|
|
assert_eq!(
|
|
market_state.linked_transit_route_anchor_fallback_counts,
|
|
vec![3, 5, 8]
|
|
);
|
|
assert_eq!(
|
|
roster.entries[0].preferred_locomotive_engine_type_raw_u8,
|
|
Some(2)
|
|
);
|
|
assert!(market_state.city_connection_latch);
|
|
assert!(!market_state.linked_transit_latch);
|
|
assert_eq!(
|
|
market_state.stat_band_root_0cfb_candidates.len(),
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS
|
|
);
|
|
assert_eq!(
|
|
market_state.stat_band_root_0d7f_candidates.len(),
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS
|
|
);
|
|
assert_eq!(
|
|
market_state.stat_band_root_1c47_candidates.len(),
|
|
SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS
|
|
);
|
|
assert_eq!(
|
|
market_state.stat_band_root_0cfb_candidates[31].label,
|
|
"stat_band_0cfb_word_32"
|
|
);
|
|
assert_eq!(
|
|
market_state.stat_band_root_0cfb_candidates[31].relative_offset_hex,
|
|
"0xd77"
|
|
);
|
|
assert_eq!(roster.entries[1].company_id, 2);
|
|
assert_eq!(roster.entries[1].current_cash, -25_000);
|
|
assert_eq!(roster.entries[1].linked_chairman_profile_id, Some(2));
|
|
assert_eq!(roster.entries[1].debt, 500_000);
|
|
assert_eq!(roster.entries[1].available_track_laying_capacity, None);
|
|
assert_eq!(roster.entries[1].takeover_cooldown_year, Some(1871));
|
|
let second_market_state = roster.entries[1]
|
|
.market_state
|
|
.as_ref()
|
|
.expect("second company market state should load");
|
|
assert_eq!(
|
|
second_market_state.largest_live_bond_principal,
|
|
Some(500_000)
|
|
);
|
|
assert_eq!(
|
|
second_market_state.highest_coupon_live_bond_principal,
|
|
Some(500_000)
|
|
);
|
|
assert_eq!(second_market_state.chairman_bonus_year, 0);
|
|
assert_eq!(second_market_state.chairman_bonus_amount, 0);
|
|
assert_eq!(second_market_state.last_dividend_year, 1850);
|
|
assert_eq!(second_market_state.current_issue_calendar_word, 3);
|
|
assert_eq!(second_market_state.current_issue_calendar_word_2, 4);
|
|
assert_eq!(second_market_state.prior_issue_calendar_word, 2);
|
|
assert_eq!(second_market_state.prior_issue_calendar_word_2, 3);
|
|
assert_eq!(
|
|
second_market_state.linked_transit_route_anchor_entry_id,
|
|
Some(41)
|
|
);
|
|
assert_eq!(
|
|
second_market_state.linked_transit_route_anchor_fallback_counts,
|
|
vec![13, 21, 34]
|
|
);
|
|
assert_eq!(
|
|
roster.entries[1].preferred_locomotive_engine_type_raw_u8,
|
|
Some(1)
|
|
);
|
|
assert!(!second_market_state.city_connection_latch);
|
|
assert!(second_market_state.linked_transit_latch);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_save_chairman_profile_table_probe_from_direct_records() {
|
|
let metadata_tag_offset = 0x40usize;
|
|
let stride = 0xcabusize;
|
|
let count = 2usize;
|
|
let start_offset = 0x4eusize;
|
|
let total_len = metadata_tag_offset + 4 + start_offset + stride * count + 0x200;
|
|
let mut bytes = vec![0u8; total_len];
|
|
bytes[metadata_tag_offset..metadata_tag_offset + 4]
|
|
.copy_from_slice(&0x00005209u32.to_le_bytes());
|
|
let header_words = [
|
|
1u32,
|
|
0xcab,
|
|
8,
|
|
6,
|
|
8,
|
|
count as u32,
|
|
1,
|
|
1,
|
|
0,
|
|
0,
|
|
1,
|
|
1,
|
|
1,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
0,
|
|
];
|
|
for (index, word) in header_words.into_iter().enumerate() {
|
|
let offset = metadata_tag_offset + 4 + index * 4;
|
|
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
|
|
}
|
|
let records_tag_offset = metadata_tag_offset + 4 + 0x100;
|
|
let close_tag_offset = records_tag_offset + 4;
|
|
bytes[records_tag_offset..records_tag_offset + 4]
|
|
.copy_from_slice(&0x0000520au32.to_le_bytes());
|
|
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
|
|
|
|
for (index, (name, linked, cash, cache0, cache1, cache4, holdings)) in [
|
|
(
|
|
"Collis Huntington",
|
|
1u32,
|
|
-107644.0f64,
|
|
252508.0f64,
|
|
0.0f64,
|
|
0.0f64,
|
|
vec![(1u32, 6000u32)],
|
|
),
|
|
(
|
|
"Thomas Durant",
|
|
2u32,
|
|
-382718.0f64,
|
|
-283562.0f64,
|
|
822000.0f64,
|
|
1_392_000.0f64,
|
|
vec![(2u32, 9000u32)],
|
|
),
|
|
]
|
|
.into_iter()
|
|
.enumerate()
|
|
{
|
|
let record_offset = metadata_tag_offset + 4 + start_offset + index * stride;
|
|
bytes[record_offset..record_offset + 4]
|
|
.copy_from_slice(&((index + 1) as u32).to_le_bytes());
|
|
bytes[record_offset + 4..record_offset + 8].copy_from_slice(&1u32.to_le_bytes());
|
|
bytes[record_offset + SAVE_CHAIRMAN_RECORD_NAME_OFFSET
|
|
..record_offset + SAVE_CHAIRMAN_RECORD_NAME_OFFSET + name.len()]
|
|
.copy_from_slice(name.as_bytes());
|
|
bytes[record_offset + SAVE_CHAIRMAN_RECORD_CASH_OFFSET
|
|
..record_offset + SAVE_CHAIRMAN_RECORD_CASH_OFFSET + 8]
|
|
.copy_from_slice(&cash.to_le_bytes());
|
|
bytes[record_offset + SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET
|
|
..record_offset + SAVE_CHAIRMAN_RECORD_LINKED_COMPANY_OFFSET + 4]
|
|
.copy_from_slice(&linked.to_le_bytes());
|
|
bytes[record_offset + SAVE_CHAIRMAN_RECORD_PERSONALITY_BYTE_0X291_OFFSET] =
|
|
(index as u8) + 10;
|
|
bytes[record_offset + SAVE_CHAIRMAN_RECORD_CACHE_0_OFFSET
|
|
..record_offset + SAVE_CHAIRMAN_RECORD_CACHE_0_OFFSET + 8]
|
|
.copy_from_slice(&cache0.to_le_bytes());
|
|
bytes[record_offset + SAVE_CHAIRMAN_RECORD_CACHE_1_OFFSET
|
|
..record_offset + SAVE_CHAIRMAN_RECORD_CACHE_1_OFFSET + 8]
|
|
.copy_from_slice(&cache1.to_le_bytes());
|
|
bytes[record_offset + 0x211..record_offset + 0x211 + 8]
|
|
.copy_from_slice(&cache4.to_le_bytes());
|
|
for (company_id, units) in holdings {
|
|
let slot_offset = record_offset
|
|
+ SAVE_CHAIRMAN_RECORD_HOLDINGS_BASE_OFFSET
|
|
+ company_id as usize * 4;
|
|
bytes[slot_offset..slot_offset + 4].copy_from_slice(&units.to_le_bytes());
|
|
}
|
|
}
|
|
|
|
let header_probe = parse_save_chairman_profile_collection_header_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
)
|
|
.expect("chairman header probe should parse");
|
|
let table = parse_save_chairman_profile_table_probe(
|
|
&bytes,
|
|
Some(&header_probe),
|
|
Some(&SmpSaveWorldSelectionContextProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-direct-world-block".to_string(),
|
|
semantic_family: "scenario-selected-company-and-chairman-context".to_string(),
|
|
chunk_tag_offset: 0,
|
|
payload_offset: 0,
|
|
payload_len: 0,
|
|
payload_len_hex: "0x0".to_string(),
|
|
selected_company_id_offset: 0,
|
|
selected_company_id: 2,
|
|
selected_company_id_hex: "0x00000002".to_string(),
|
|
selected_chairman_profile_id_offset: 0,
|
|
selected_chairman_profile_id: 2,
|
|
selected_chairman_profile_id_hex: "0x00000002".to_string(),
|
|
chairman_slot_selector_offset: 0,
|
|
chairman_slot_selectors: vec![],
|
|
campaign_override_flag_offset: 0,
|
|
campaign_override_flag: 0,
|
|
campaign_override_flag_hex: "0x00".to_string(),
|
|
chairman_role_gate_offset: 0,
|
|
chairman_role_gate_bytes: vec![],
|
|
evidence: vec![],
|
|
}),
|
|
Some(&SmpSaveTaggedCollectionHeaderProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-company-tagged-header-counts".to_string(),
|
|
semantic_family: "scenario-save-company-header-counts".to_string(),
|
|
metadata_tag_offset: 0,
|
|
records_tag_offset: 0,
|
|
close_tag_offset: 0,
|
|
direct_collection_flag: 1,
|
|
direct_collection_flag_hex: "0x00000001".to_string(),
|
|
direct_record_stride: 0x7684,
|
|
direct_record_stride_hex: "0x00007684".to_string(),
|
|
live_id_bound: 5,
|
|
live_id_bound_hex: "0x00000005".to_string(),
|
|
live_record_count: 2,
|
|
live_record_count_hex: "0x00000002".to_string(),
|
|
header_words: vec![],
|
|
header_hex_words: vec![],
|
|
evidence: vec![],
|
|
}),
|
|
)
|
|
.expect("chairman profile table should parse");
|
|
|
|
assert_eq!(table.observed_entry_count, 2);
|
|
assert_eq!(table.selected_chairman_profile_id, Some(2));
|
|
assert_eq!(table.entries.len(), 2);
|
|
assert_eq!(table.entries[0].profile_id, 1);
|
|
assert_eq!(table.entries[0].name, "Collis Huntington");
|
|
assert_eq!(table.entries[0].linked_company_id, Some(1));
|
|
assert_eq!(table.entries[0].company_holdings.get(&1), Some(&6000));
|
|
assert_eq!(table.entries[0].current_cash, -107644);
|
|
assert_eq!(table.entries[0].holdings_value_total, 252508);
|
|
assert_eq!(table.entries[0].purchasing_power_total, 144864);
|
|
assert_eq!(table.entries[0].personality_byte_0x291, Some(10));
|
|
assert_eq!(table.entries[1].profile_id, 2);
|
|
assert_eq!(table.entries[1].company_holdings.get(&2), Some(&9000));
|
|
assert_eq!(table.entries[1].holdings_value_total, 822000);
|
|
assert_eq!(table.entries[1].purchasing_power_total, 1_009_282);
|
|
assert_eq!(table.entries[1].personality_byte_0x291, Some(11));
|
|
}
|
|
|
|
#[test]
|
|
fn builds_save_world_selection_role_analysis_from_probe() {
|
|
let probe = SmpSaveWorldSelectionContextProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
source_kind: "save-direct-world-block".to_string(),
|
|
semantic_family: "scenario-selected-company-and-chairman-context".to_string(),
|
|
chunk_tag_offset: 0,
|
|
payload_offset: 0,
|
|
payload_len: 0x4f2c,
|
|
payload_len_hex: "0x4f2c".to_string(),
|
|
selected_company_id_offset: 0x21,
|
|
selected_company_id: 3,
|
|
selected_company_id_hex: "0x00000003".to_string(),
|
|
selected_chairman_profile_id_offset: 0x25,
|
|
selected_chairman_profile_id: 7,
|
|
selected_chairman_profile_id_hex: "0x00000007".to_string(),
|
|
chairman_slot_selector_offset: 0x87,
|
|
chairman_slot_selectors: vec![1, 0, 2, 0],
|
|
campaign_override_flag_offset: 0xc5,
|
|
campaign_override_flag: 1,
|
|
campaign_override_flag_hex: "0x01".to_string(),
|
|
chairman_role_gate_offset: 0x0bc3,
|
|
chairman_role_gate_bytes: vec![2, 0, 1, 0],
|
|
evidence: vec![],
|
|
};
|
|
|
|
let analysis = build_save_world_selection_role_analysis(&probe);
|
|
|
|
assert_eq!(analysis.selected_company_id, 3);
|
|
assert_eq!(analysis.selected_chairman_profile_id, 7);
|
|
assert_eq!(analysis.campaign_override_flag_hex, "0x01");
|
|
assert_eq!(analysis.chairman_slots.len(), 4);
|
|
assert_eq!(analysis.chairman_slots[0].selector_byte_hex, "0x01");
|
|
assert_eq!(analysis.chairman_slots[2].role_gate_byte_hex, "0x01");
|
|
}
|
|
|
|
#[test]
|
|
fn builds_save_candidate_views_with_raw_bits() {
|
|
let mut bytes = vec![0u8; 0x40];
|
|
bytes[0x08..0x0c].copy_from_slice(&0x3f800000u32.to_le_bytes());
|
|
bytes[0x10..0x18].copy_from_slice(&(-2458.0f64).to_le_bytes());
|
|
|
|
let dword = build_save_dword_candidate(&bytes, 0, "unit_float", 0x08)
|
|
.expect("dword candidate should build");
|
|
let qword =
|
|
build_save_qword_candidate(&bytes, 0, 0x10).expect("qword candidate should build");
|
|
|
|
assert_eq!(dword.raw_u32_hex, "0x3f800000");
|
|
assert_eq!(dword.value_i32, 1_065_353_216);
|
|
assert_eq!(dword.value_f32, 1.0);
|
|
assert_eq!(qword.raw_u64, (-2458.0f64).to_bits());
|
|
assert_eq!(qword.value_i64, (-2458.0f64).to_bits() as i64);
|
|
assert_eq!(qword.value_f64, -2458.0);
|
|
}
|
|
|
|
#[test]
|
|
fn derives_chairman_holdings_share_price_total_from_grounded_company_prices() {
|
|
let holdings_by_company =
|
|
BTreeMap::from([(2u32, 19_000u32), (4u32, 1_000u32), (6u32, 2_000u32)]);
|
|
let company_share_prices = BTreeMap::from([(2u32, 66i64), (4u32, 69i64), (6u32, 27i64)]);
|
|
|
|
let total =
|
|
derive_chairman_holdings_share_price_total(&holdings_by_company, &company_share_prices)
|
|
.expect("derived holdings total should compute");
|
|
|
|
assert_eq!(total, 1_377_000);
|
|
}
|
|
|
|
#[test]
|
|
fn derives_chairman_cached_purchasing_power_from_strongest_nonnegative_cache() {
|
|
let cached_scalar_candidates = vec![
|
|
SmpSaveScalarCandidate {
|
|
relative_offset: 0x1e9,
|
|
relative_offset_hex: "0x1e9".to_string(),
|
|
raw_u64: (-343_508.0f64).to_bits(),
|
|
raw_u64_hex: format!("0x{:016x}", (-343_508.0f64).to_bits()),
|
|
value_i64: round_f64_to_i64(-343_508.0).expect("i64"),
|
|
value_f64: -343_508.0,
|
|
},
|
|
SmpSaveScalarCandidate {
|
|
relative_offset: 0x201,
|
|
relative_offset_hex: "0x201".to_string(),
|
|
raw_u64: 1_386_000.0f64.to_bits(),
|
|
raw_u64_hex: format!("0x{:016x}", 1_386_000.0f64.to_bits()),
|
|
value_i64: round_f64_to_i64(1_386_000.0).expect("i64"),
|
|
value_f64: 1_386_000.0,
|
|
},
|
|
SmpSaveScalarCandidate {
|
|
relative_offset: 0x211,
|
|
relative_offset_hex: "0x211".to_string(),
|
|
raw_u64: 1_392_000.0f64.to_bits(),
|
|
raw_u64_hex: format!("0x{:016x}", 1_392_000.0f64.to_bits()),
|
|
value_i64: round_f64_to_i64(1_392_000.0).expect("i64"),
|
|
value_f64: 1_392_000.0,
|
|
},
|
|
];
|
|
|
|
let total =
|
|
derive_chairman_cached_purchasing_power_total(-463_436, &cached_scalar_candidates)
|
|
.expect("derived purchasing power should compute");
|
|
|
|
assert_eq!(total, 928_564);
|
|
}
|
|
|
|
#[test]
|
|
fn classifies_rt3_105_post_span_bridge_variants() {
|
|
let base_trailer = SmpRuntimeTrailerBlock {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
trailer_family: "rt3-105-save-trailer-v1".to_string(),
|
|
trailer_evidence: vec![],
|
|
trailer_offset: 944,
|
|
prefix_words_0_to_5: vec![],
|
|
prefix_hex_words_0_to_5: vec![],
|
|
tag_word_6: 0,
|
|
tag_word_6_hex: String::new(),
|
|
tag_chunk_id_u16: 0x2ee1,
|
|
tag_chunk_id_hex: "0x2ee1".to_string(),
|
|
tag_chunk_id_grounded_alignment: None,
|
|
length_word_7: 0x32c8_0000,
|
|
length_word_7_hex: "0x32c80000".to_string(),
|
|
length_high_u16: 0x32c8,
|
|
length_high_hex: "0x32c8".to_string(),
|
|
selector_word_8: 0x7110_0000,
|
|
selector_word_8_hex: "0x71100000".to_string(),
|
|
selector_high_u16: 0x7110,
|
|
selector_high_hex: "0x7110".to_string(),
|
|
layout_word_9: 0,
|
|
layout_word_9_hex: String::new(),
|
|
descriptor_word_10: 0x7801_0000,
|
|
descriptor_word_10_hex: "0x78010000".to_string(),
|
|
descriptor_high_u16: 0x7801,
|
|
descriptor_high_hex: "0x7801".to_string(),
|
|
descriptor_word_11: 0,
|
|
descriptor_word_11_hex: String::new(),
|
|
counter_word_12: 0,
|
|
counter_word_12_hex: String::new(),
|
|
offset_word_13: 0,
|
|
offset_word_13_hex: String::new(),
|
|
span_word_14: 0,
|
|
span_word_14_hex: String::new(),
|
|
mode_word_15: 0,
|
|
mode_word_15_hex: String::new(),
|
|
words: vec![],
|
|
hex_words: vec![],
|
|
};
|
|
let base_post_span = SmpRuntimePostSpanProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
span_target_offset: 13944,
|
|
next_nonzero_offset: Some(14795),
|
|
next_aligned_candidate_offset: Some(20244),
|
|
next_aligned_candidate_words: vec![],
|
|
next_aligned_candidate_hex_words: vec![],
|
|
header_candidates: vec![SmpRuntimePostSpanHeaderCandidate {
|
|
offset: 20244,
|
|
words: vec![],
|
|
hex_words: vec![],
|
|
dense_word_count: 3,
|
|
high_u16_words: vec![0x6200, 0x0000, 0xfff7, 0x5515],
|
|
high_hex_words: vec![],
|
|
grounded_alignments: vec![],
|
|
}],
|
|
grounded_progress_hits: vec![],
|
|
};
|
|
let base_profile = SmpRt3105PackedProfileProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
packed_profile_offset: 29632,
|
|
packed_profile_len: 0x108,
|
|
packed_profile_len_hex: "0x108".to_string(),
|
|
packed_profile_block: SmpRt3105PackedProfileBlock {
|
|
relative_len: 0x108,
|
|
relative_len_hex: "0x108".to_string(),
|
|
leading_word_0: 3,
|
|
leading_word_0_hex: "0x00000003".to_string(),
|
|
trailing_zero_word_count_after_leading_word: 2,
|
|
header_flag_word_3: 0x0100_0000,
|
|
header_flag_word_3_hex: "0x01000000".to_string(),
|
|
map_path_offset: 0x10,
|
|
map_path: Some("Alternate USA.gmp".to_string()),
|
|
display_name_offset: 0x43,
|
|
display_name: Some("Alternate USA".to_string()),
|
|
profile_byte_0x77: 0x07,
|
|
profile_byte_0x77_hex: "0x07".to_string(),
|
|
profile_byte_0x82: 0x4d,
|
|
profile_byte_0x82_hex: "0x4d".to_string(),
|
|
profile_byte_0x97: 0,
|
|
profile_byte_0x97_hex: "0x00".to_string(),
|
|
profile_byte_0xc5: 0,
|
|
profile_byte_0xc5_hex: "0x00".to_string(),
|
|
stable_nonzero_words: vec![],
|
|
},
|
|
ascii_runs: vec![],
|
|
};
|
|
let base_bridge = parse_rt3_105_post_span_bridge_probe(
|
|
Some(&base_trailer),
|
|
Some(&base_post_span),
|
|
Some(&base_profile),
|
|
)
|
|
.expect("base bridge should parse");
|
|
assert_eq!(
|
|
base_bridge.bridge_family,
|
|
"rt3-105-save-post-span-bridge-v1"
|
|
);
|
|
assert_eq!(base_bridge.packed_profile_delta_from_span_target, 15688);
|
|
assert_eq!(
|
|
base_bridge.next_candidate_delta_from_packed_profile,
|
|
Some(-9388)
|
|
);
|
|
let base_variant_trailer = SmpRuntimeTrailerBlock {
|
|
descriptor_word_10: 0x7401_0000,
|
|
descriptor_word_10_hex: "0x74010000".to_string(),
|
|
descriptor_high_u16: 0x7401,
|
|
descriptor_high_hex: "0x7401".to_string(),
|
|
..base_trailer.clone()
|
|
};
|
|
let base_variant_bridge = parse_rt3_105_post_span_bridge_probe(
|
|
Some(&base_variant_trailer),
|
|
Some(&base_post_span),
|
|
Some(&base_profile),
|
|
)
|
|
.expect("base bridge variant should parse");
|
|
assert_eq!(
|
|
base_variant_bridge.bridge_family,
|
|
"rt3-105-save-post-span-bridge-v1"
|
|
);
|
|
|
|
let alt_trailer = SmpRuntimeTrailerBlock {
|
|
profile_family: "rt3-105-alt-save-container-v1".to_string(),
|
|
selector_word_8: 0x54cd_0000,
|
|
selector_word_8_hex: "0x54cd0000".to_string(),
|
|
selector_high_u16: 0x54cd,
|
|
selector_high_hex: "0x54cd".to_string(),
|
|
descriptor_word_10: 0x5901_0000,
|
|
descriptor_word_10_hex: "0x59010000".to_string(),
|
|
descriptor_high_u16: 0x5901,
|
|
descriptor_high_hex: "0x5901".to_string(),
|
|
..base_trailer.clone()
|
|
};
|
|
let alt_post_span = SmpRuntimePostSpanProbe {
|
|
profile_family: "rt3-105-alt-save-container-v1".to_string(),
|
|
next_aligned_candidate_offset: Some(29892),
|
|
header_candidates: vec![SmpRuntimePostSpanHeaderCandidate {
|
|
offset: 29892,
|
|
words: vec![],
|
|
hex_words: vec![],
|
|
dense_word_count: 3,
|
|
high_u16_words: vec![0x1500, 0x0100, 0x4100, 0x0200],
|
|
high_hex_words: vec![],
|
|
grounded_alignments: vec![],
|
|
}],
|
|
..base_post_span.clone()
|
|
};
|
|
let alt_profile = SmpRt3105PackedProfileProbe {
|
|
profile_family: "rt3-105-alt-save-container-v1".to_string(),
|
|
packed_profile_block: SmpRt3105PackedProfileBlock {
|
|
map_path: Some("Spanish Mainline.gmp".to_string()),
|
|
display_name: Some("Spanish Mainline".to_string()),
|
|
profile_byte_0x82: 0xa3,
|
|
profile_byte_0x82_hex: "0xa3".to_string(),
|
|
..base_profile.packed_profile_block.clone()
|
|
},
|
|
..base_profile.clone()
|
|
};
|
|
let alt_bridge = parse_rt3_105_post_span_bridge_probe(
|
|
Some(&alt_trailer),
|
|
Some(&alt_post_span),
|
|
Some(&alt_profile),
|
|
)
|
|
.expect("alt bridge should parse");
|
|
assert_eq!(
|
|
alt_bridge.bridge_family,
|
|
"rt3-105-alt-save-post-span-bridge-v1"
|
|
);
|
|
assert_eq!(
|
|
alt_bridge.next_candidate_delta_from_packed_profile,
|
|
Some(260)
|
|
);
|
|
|
|
let scenario_trailer = SmpRuntimeTrailerBlock {
|
|
profile_family: "rt3-105-scenario-save-container-v1".to_string(),
|
|
trailer_family: "unknown".to_string(),
|
|
trailer_offset: 864,
|
|
length_word_7: 0,
|
|
length_word_7_hex: "0x00000000".to_string(),
|
|
length_high_u16: 0,
|
|
length_high_hex: "0x0000".to_string(),
|
|
selector_word_8: 0x0001_86a0,
|
|
selector_word_8_hex: "0x000186a0".to_string(),
|
|
selector_high_u16: 0x0001,
|
|
selector_high_hex: "0x0001".to_string(),
|
|
descriptor_word_10: 0x0186_a000,
|
|
descriptor_word_10_hex: "0x0186a000".to_string(),
|
|
descriptor_high_u16: 0x0186,
|
|
descriptor_high_hex: "0x0186".to_string(),
|
|
..base_trailer.clone()
|
|
};
|
|
let scenario_post_span = SmpRuntimePostSpanProbe {
|
|
profile_family: "rt3-105-scenario-save-container-v1".to_string(),
|
|
span_target_offset: 864,
|
|
next_aligned_candidate_offset: Some(940),
|
|
header_candidates: vec![SmpRuntimePostSpanHeaderCandidate {
|
|
offset: 940,
|
|
words: vec![],
|
|
hex_words: vec![],
|
|
dense_word_count: 3,
|
|
high_u16_words: vec![0x0186, 0x0006, 0x0006, 0x0001],
|
|
high_hex_words: vec![],
|
|
grounded_alignments: vec![],
|
|
}],
|
|
..base_post_span.clone()
|
|
};
|
|
let scenario_profile = SmpRt3105PackedProfileProbe {
|
|
profile_family: "rt3-105-scenario-save-container-v1".to_string(),
|
|
packed_profile_block: SmpRt3105PackedProfileBlock {
|
|
map_path: Some("Southern Pacific.gmp".to_string()),
|
|
display_name: Some("Southern Pacific".to_string()),
|
|
profile_byte_0x82: 0x90,
|
|
profile_byte_0x82_hex: "0x90".to_string(),
|
|
..base_profile.packed_profile_block.clone()
|
|
},
|
|
..base_profile.clone()
|
|
};
|
|
let scenario_bridge = parse_rt3_105_post_span_bridge_probe(
|
|
Some(&scenario_trailer),
|
|
Some(&scenario_post_span),
|
|
Some(&scenario_profile),
|
|
)
|
|
.expect("scenario bridge should parse");
|
|
assert_eq!(
|
|
scenario_bridge.bridge_family,
|
|
"rt3-105-scenario-post-span-bridge-v1"
|
|
);
|
|
assert_eq!(
|
|
scenario_bridge.next_candidate_delta_from_packed_profile,
|
|
Some(-28692)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_rt3_105_save_bridge_payload_probe() {
|
|
let mut bytes = vec![0u8; 0x7000];
|
|
let primary = 0x4f14usize;
|
|
let secondary = 0x671cusize;
|
|
let primary_words: [u32; 8] = [
|
|
0x62000000, 0x00000000, 0xfff70000, 0x55150000, 0x55550000, 0x00000000, 0xfff70000,
|
|
0x54550000,
|
|
];
|
|
for (index, word) in primary_words.iter().enumerate() {
|
|
bytes[primary + index * 4..primary + (index + 1) * 4]
|
|
.copy_from_slice(&(*word).to_le_bytes());
|
|
}
|
|
|
|
let secondary_words: [u32; 8] = [
|
|
0x00050000, 0x00050005, 0xfff70000, 0x54540000, 0x545400f9, 0x00f900f9, 0x00f94008,
|
|
0x00001555,
|
|
];
|
|
for (index, word) in secondary_words.iter().enumerate() {
|
|
bytes[secondary + index * 4..secondary + (index + 1) * 4]
|
|
.copy_from_slice(&(*word).to_le_bytes());
|
|
}
|
|
|
|
let bridge_probe = SmpRt3105PostSpanBridgeProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
bridge_family: "rt3-105-save-post-span-bridge-v1".to_string(),
|
|
bridge_evidence: vec![],
|
|
span_target_offset: 0x3678,
|
|
next_candidate_offset: Some(primary),
|
|
next_candidate_delta_from_span_target: Some(primary - 0x3678),
|
|
packed_profile_offset: 0x73c0,
|
|
packed_profile_delta_from_span_target: 0x3d48,
|
|
next_candidate_delta_from_packed_profile: Some(primary as i64 - 0x73c0),
|
|
selector_high_u16: 0x7110,
|
|
selector_high_hex: "0x7110".to_string(),
|
|
descriptor_high_u16: 0x7801,
|
|
descriptor_high_hex: "0x7801".to_string(),
|
|
next_candidate_high_u16_words: vec![0x6200, 0x0000, 0xfff7, 0x5515],
|
|
next_candidate_high_hex_words: vec![],
|
|
};
|
|
|
|
let probe = parse_rt3_105_save_bridge_payload_probe(&bytes, Some(&bridge_probe))
|
|
.expect("save bridge payload probe should parse");
|
|
|
|
assert_eq!(probe.primary_block_offset, primary);
|
|
assert_eq!(probe.primary_block_len, 0x20);
|
|
assert_eq!(probe.secondary_block_offset, secondary);
|
|
assert_eq!(probe.secondary_block_delta_from_primary, 0x1808);
|
|
assert_eq!(probe.secondary_block_end_offset, 0x73c0);
|
|
assert_eq!(probe.secondary_block_len, 0xca4);
|
|
assert_eq!(probe.primary_words[..4], primary_words[..4]);
|
|
assert_eq!(probe.secondary_words[..8], secondary_words[..8]);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_rt3_105_save_name_table_probe() {
|
|
let mut bytes = vec![0u8; 0x7400];
|
|
let secondary = 0x671cusize;
|
|
let header = secondary + 0x354;
|
|
let entries = secondary + 0x3b5;
|
|
let stride = 0x22usize;
|
|
let names = ["AluminumMill", "Nuclear Power Plant", "Bakery"];
|
|
|
|
bytes[header..header + 4].copy_from_slice(&0x10000000u32.to_le_bytes());
|
|
bytes[header + 4..header + 8].copy_from_slice(&0x00009000u32.to_le_bytes());
|
|
bytes[header + 8..header + 12].copy_from_slice(&0x0000332eu32.to_le_bytes());
|
|
bytes[header + 0x1c..header + 0x20].copy_from_slice(&4u32.to_le_bytes());
|
|
bytes[header + 0x20..header + 0x24].copy_from_slice(&(names.len() as u32).to_le_bytes());
|
|
bytes[header + 12..header + 16].copy_from_slice(&1u32.to_le_bytes());
|
|
bytes[header + 16..header + 20].copy_from_slice(&0x22u32.to_le_bytes());
|
|
bytes[header + 20..header + 24].copy_from_slice(&2u32.to_le_bytes());
|
|
bytes[header + 24..header + 28].copy_from_slice(&2u32.to_le_bytes());
|
|
bytes[header + 0x28..header + 0x2c].copy_from_slice(&1u32.to_le_bytes());
|
|
|
|
for (index, name) in names.iter().enumerate() {
|
|
let off = entries + index * stride;
|
|
let raw = &mut bytes[off..off + stride];
|
|
raw[..name.len()].copy_from_slice(name.as_bytes());
|
|
let trailer = if *name == "Nuclear Power Plant" {
|
|
0u32
|
|
} else {
|
|
1u32
|
|
};
|
|
raw[stride - 4..stride].copy_from_slice(&trailer.to_le_bytes());
|
|
}
|
|
let footer = entries + names.len() * stride;
|
|
bytes[footer..footer + 4].copy_from_slice(&0x32dcu32.to_le_bytes());
|
|
bytes[footer + 4..footer + 8].copy_from_slice(&0x3714u32.to_le_bytes());
|
|
bytes[footer + 8] = 0x00;
|
|
|
|
let payload = SmpRt3105SaveBridgePayloadProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
bridge_family: "rt3-105-save-post-span-bridge-v1".to_string(),
|
|
primary_block_offset: 0x4f14,
|
|
primary_block_len: 0x20,
|
|
primary_block_len_hex: "0x20".to_string(),
|
|
primary_words: vec![],
|
|
primary_hex_words: vec![],
|
|
secondary_block_offset: secondary,
|
|
secondary_block_delta_from_primary: 0x1808,
|
|
secondary_block_delta_from_primary_hex: "0x1808".to_string(),
|
|
secondary_block_end_offset: footer + 9,
|
|
secondary_block_len: footer + 9 - secondary,
|
|
secondary_block_len_hex: format!("0x{:x}", footer + 9 - secondary),
|
|
secondary_preview_word_count: 32,
|
|
secondary_words: vec![],
|
|
secondary_hex_words: vec![],
|
|
evidence: vec![],
|
|
};
|
|
|
|
let probe = parse_rt3_105_save_name_table_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
Some(&payload),
|
|
)
|
|
.expect("save name table probe should parse");
|
|
|
|
assert_eq!(probe.source_kind, "save-bridge-secondary-block");
|
|
assert_eq!(
|
|
probe.semantic_family,
|
|
"scenario-named-candidate-availability-table"
|
|
);
|
|
assert_eq!(probe.header_offset, header);
|
|
assert_eq!(probe.entry_stride, stride);
|
|
assert_eq!(probe.observed_entry_capacity, 4);
|
|
assert_eq!(probe.observed_entry_count, names.len());
|
|
assert_eq!(probe.entries[0].text, "AluminumMill");
|
|
assert_eq!(probe.entries[0].availability_dword, 1);
|
|
assert_eq!(probe.entries[2].text, "Bakery");
|
|
assert_eq!(probe.zero_trailer_entry_count, 1);
|
|
assert_eq!(
|
|
probe.zero_trailer_entry_names,
|
|
vec!["Nuclear Power Plant".to_string()]
|
|
);
|
|
assert_eq!(probe.trailing_footer_hex, "dc3200001437000000");
|
|
assert_eq!(probe.footer_progress_word_0, 0x32dc);
|
|
assert_eq!(probe.footer_progress_word_1, 0x3714);
|
|
assert_eq!(probe.footer_trailing_byte, 0x00);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_rt3_105_map_name_table_probe_from_fixed_offsets() {
|
|
let mut bytes = vec![0u8; 0x7400];
|
|
let header = 0x6a70usize;
|
|
let entries = 0x6ad1usize;
|
|
let stride = 0x22usize;
|
|
let observed_entry_count = 67usize;
|
|
|
|
bytes[header..header + 4].copy_from_slice(&0x00000000u32.to_le_bytes());
|
|
bytes[header + 4..header + 8].copy_from_slice(&0x00000000u32.to_le_bytes());
|
|
bytes[header + 8..header + 12].copy_from_slice(&0x0000332eu32.to_le_bytes());
|
|
bytes[header + 12..header + 16].copy_from_slice(&1u32.to_le_bytes());
|
|
bytes[header + 16..header + 20].copy_from_slice(&0x22u32.to_le_bytes());
|
|
bytes[header + 20..header + 24].copy_from_slice(&2u32.to_le_bytes());
|
|
bytes[header + 24..header + 28].copy_from_slice(&2u32.to_le_bytes());
|
|
bytes[header + 0x1c..header + 0x20].copy_from_slice(&0x44u32.to_le_bytes());
|
|
bytes[header + 0x20..header + 0x24]
|
|
.copy_from_slice(&(observed_entry_count as u32).to_le_bytes());
|
|
bytes[header + 0x28..header + 0x2c].copy_from_slice(&1u32.to_le_bytes());
|
|
|
|
for index in 0..observed_entry_count {
|
|
let name = match index {
|
|
0 => "AutoPlant".to_string(),
|
|
1 => "Nuclear Power Plant".to_string(),
|
|
66 => "Warehouse11".to_string(),
|
|
_ => format!("Entry{index:02}"),
|
|
};
|
|
let off = entries + index * stride;
|
|
let raw = &mut bytes[off..off + stride];
|
|
raw[..name.len()].copy_from_slice(name.as_bytes());
|
|
let trailer = if name == "Nuclear Power Plant" {
|
|
0u32
|
|
} else {
|
|
1u32
|
|
};
|
|
raw[stride - 4..stride].copy_from_slice(&trailer.to_le_bytes());
|
|
}
|
|
|
|
let footer = entries + observed_entry_count * stride;
|
|
bytes[footer..footer + 4].copy_from_slice(&0x32dcu32.to_le_bytes());
|
|
bytes[footer + 4..footer + 8].copy_from_slice(&0x3714u32.to_le_bytes());
|
|
bytes[footer + 8] = 0x00;
|
|
|
|
let probe = parse_rt3_105_save_name_table_probe(
|
|
&bytes,
|
|
Some("gmp"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-map-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
None,
|
|
)
|
|
.expect("map name table probe should parse");
|
|
|
|
assert_eq!(probe.profile_family, "rt3-105-map-container-v1");
|
|
assert_eq!(probe.source_kind, "map-fixed-catalog-range");
|
|
assert_eq!(probe.header_offset, header);
|
|
assert_eq!(probe.entries_offset, entries);
|
|
assert_eq!(probe.observed_entry_count, observed_entry_count);
|
|
assert_eq!(probe.entries[0].text, "AutoPlant");
|
|
assert_eq!(probe.entries[66].text, "Warehouse11");
|
|
assert_eq!(
|
|
probe.zero_trailer_entry_names,
|
|
vec!["Nuclear Power Plant".to_string()]
|
|
);
|
|
assert_eq!(probe.footer_progress_word_0, 0x32dc);
|
|
assert_eq!(probe.footer_progress_word_1, 0x3714);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_rt3_105_save_named_locomotive_availability_probe() {
|
|
let mut bytes = vec![0u8; 0x9000];
|
|
let packed_profile_offset = 0x73c0usize;
|
|
let packed_profile_len = 0x108usize;
|
|
let entries_offset = 0x7c78usize;
|
|
let names = [
|
|
("Eight Wheeler 4-4-0", 1u32),
|
|
("EP-2 Bipolar", 1u32),
|
|
("ET22", 1u32),
|
|
("F3", 0u32),
|
|
("Fairlie 0-6-6-0", 1u32),
|
|
("Firefly 2-2-2", 0u32),
|
|
("FP45", 0u32),
|
|
("Ge 6/6 Crocodile", 1u32),
|
|
("GG1", 0u32),
|
|
("GP7", 1u32),
|
|
];
|
|
|
|
for (index, (name, value)) in names.iter().enumerate() {
|
|
let offset = entries_offset + index * RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE;
|
|
bytes[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
|
|
bytes[offset + 4..offset + 4 + name.len()].copy_from_slice(name.as_bytes());
|
|
}
|
|
|
|
let probe = parse_rt3_105_save_named_locomotive_availability_probe(
|
|
&bytes,
|
|
Some("gms"),
|
|
Some(&SmpContainerProfile {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
profile_evidence: vec![],
|
|
is_known_profile: true,
|
|
}),
|
|
Some(&SmpRt3105PackedProfileProbe {
|
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
|
packed_profile_offset,
|
|
packed_profile_len,
|
|
packed_profile_len_hex: "0x108".to_string(),
|
|
packed_profile_block: SmpRt3105PackedProfileBlock {
|
|
relative_len: packed_profile_len,
|
|
relative_len_hex: "0x108".to_string(),
|
|
leading_word_0: 3,
|
|
leading_word_0_hex: "0x00000003".to_string(),
|
|
trailing_zero_word_count_after_leading_word: 2,
|
|
header_flag_word_3: 1,
|
|
header_flag_word_3_hex: "0x00000001".to_string(),
|
|
map_path_offset: 0x10,
|
|
map_path: Some("Alternate USA.gmp".to_string()),
|
|
display_name_offset: 0x43,
|
|
display_name: Some("Alternate USA".to_string()),
|
|
profile_byte_0x77: 0x07,
|
|
profile_byte_0x77_hex: "0x07".to_string(),
|
|
profile_byte_0x82: 0x4d,
|
|
profile_byte_0x82_hex: "0x4d".to_string(),
|
|
profile_byte_0x97: 0,
|
|
profile_byte_0x97_hex: "0x00".to_string(),
|
|
profile_byte_0xc5: 0,
|
|
profile_byte_0xc5_hex: "0x00".to_string(),
|
|
stable_nonzero_words: vec![],
|
|
},
|
|
ascii_runs: vec![],
|
|
}),
|
|
)
|
|
.expect("save-side locomotive table probe should parse");
|
|
|
|
assert_eq!(probe.source_kind, "save-direct-locomotive-row-run");
|
|
assert_eq!(
|
|
probe.semantic_family,
|
|
"scenario-named-locomotive-availability-table"
|
|
);
|
|
assert_eq!(probe.entries_offset, entries_offset);
|
|
assert_eq!(
|
|
probe.entry_stride,
|
|
RT3_105_SAVE_NAMED_LOCOMOTIVE_ENTRY_STRIDE
|
|
);
|
|
assert_eq!(probe.observed_entry_count, names.len());
|
|
assert_eq!(probe.zero_availability_count, 4);
|
|
assert_eq!(probe.entries[0].text, "Eight Wheeler 4-4-0");
|
|
assert_eq!(probe.entries[9].text, "GP7");
|
|
}
|
|
|
|
#[test]
|
|
fn classifies_rt3_105_alt_save_container_profile() {
|
|
let shared_header = SmpSharedHeader {
|
|
byte_len: 64,
|
|
root_kind_word: 0x000025e5,
|
|
root_kind_word_hex: "0x000025e5".to_string(),
|
|
primary_family_tag: 0x00002ee0,
|
|
primary_family_tag_hex: "0x00002ee0".to_string(),
|
|
shared_signature_words_1_to_7: vec![
|
|
0x00002ee0, 0x0001c001, 0x00018000, 0x00010000, 0x00000754, 0x00000754, 0x00000754,
|
|
],
|
|
shared_signature_hex_words_1_to_7: vec![
|
|
"0x00002ee0".to_string(),
|
|
"0x0001c001".to_string(),
|
|
"0x00018000".to_string(),
|
|
"0x00010000".to_string(),
|
|
"0x00000754".to_string(),
|
|
"0x00000754".to_string(),
|
|
"0x00000754".to_string(),
|
|
],
|
|
matches_grounded_common_signature: false,
|
|
payload_window_words_8_to_9: vec![0x007a5978, 0x007a9022],
|
|
payload_window_hex_words_8_to_9: vec![
|
|
"0x007a5978".to_string(),
|
|
"0x007a9022".to_string(),
|
|
],
|
|
reserved_words_10_to_14: vec![0; 5],
|
|
reserved_words_10_to_14_all_zero: true,
|
|
final_flag_word: 0,
|
|
final_flag_word_hex: "0x00000000".to_string(),
|
|
};
|
|
let early_content_probe = SmpEarlyContentProbe {
|
|
first_post_text_nonzero_offset: 722,
|
|
zero_pad_after_text_len: 431,
|
|
first_post_text_block_len: 35,
|
|
first_post_text_block_hex:
|
|
"0101010000010000000000000100000000000000010000000000000000010100000001".to_string(),
|
|
trailing_zero_pad_after_first_block_len: 45,
|
|
secondary_nonzero_offset: Some(802),
|
|
secondary_aligned_word_window_offset: Some(800),
|
|
secondary_aligned_word_window_words: vec![
|
|
0x00010000, 0x49f00100, 0x00000002, 0xa0000000, 0x00000186, 0x00000000, 0x000186a0,
|
|
0x00000000,
|
|
],
|
|
secondary_aligned_word_window_hex_words: vec![
|
|
"0x00010000".to_string(),
|
|
"0x49f00100".to_string(),
|
|
"0x00000002".to_string(),
|
|
"0xa0000000".to_string(),
|
|
"0x00000186".to_string(),
|
|
"0x00000000".to_string(),
|
|
"0x000186a0".to_string(),
|
|
"0x00000000".to_string(),
|
|
],
|
|
secondary_preview_hex:
|
|
"01000001f04902000000000000a08601000000000000a08601000000000000a0".to_string(),
|
|
};
|
|
|
|
let header_variant = classify_header_variant_probe(&shared_header);
|
|
let secondary_variant =
|
|
classify_secondary_variant_probe(&early_content_probe).expect("secondary probe");
|
|
let container_profile = classify_container_profile(
|
|
Some("gms"),
|
|
Some(&header_variant),
|
|
Some(&secondary_variant),
|
|
)
|
|
.expect("container profile");
|
|
|
|
assert_eq!(header_variant.variant_family, "rt3-105-alt-save-header-v1");
|
|
assert_eq!(
|
|
secondary_variant.variant_family,
|
|
"rt3-105-gms-alt-family-v1"
|
|
);
|
|
assert_eq!(
|
|
container_profile.profile_family,
|
|
"rt3-105-alt-save-container-v1"
|
|
);
|
|
assert!(container_profile.is_known_profile);
|
|
}
|
|
|
|
#[test]
|
|
fn classifies_rt3_105_map_container_profiles_from_header_families() {
|
|
let scenario_profile = classify_container_profile(
|
|
Some("gmp"),
|
|
Some(&SmpHeaderVariantProbe {
|
|
variant_family: "rt3-105-scenario-save-header-v1".to_string(),
|
|
variant_evidence: vec![],
|
|
is_known_family: true,
|
|
}),
|
|
Some(&SmpSecondaryVariantProbe {
|
|
aligned_window_offset: 0,
|
|
words: vec![1, 0, 0, 0],
|
|
hex_words: vec![],
|
|
variant_family: "unknown".to_string(),
|
|
variant_evidence: vec![],
|
|
}),
|
|
)
|
|
.expect("scenario map profile");
|
|
|
|
let alt_profile = classify_container_profile(
|
|
Some("gmp"),
|
|
Some(&SmpHeaderVariantProbe {
|
|
variant_family: "rt3-105-alt-save-header-v1".to_string(),
|
|
variant_evidence: vec![],
|
|
is_known_family: true,
|
|
}),
|
|
Some(&SmpSecondaryVariantProbe {
|
|
aligned_window_offset: 0,
|
|
words: vec![0x49f00100, 2, 0xa0000000, 0x186],
|
|
hex_words: vec![],
|
|
variant_family: "unknown".to_string(),
|
|
variant_evidence: vec![],
|
|
}),
|
|
)
|
|
.expect("alt map profile");
|
|
|
|
assert_eq!(
|
|
scenario_profile.profile_family,
|
|
"rt3-105-scenario-map-container-v1"
|
|
);
|
|
assert!(scenario_profile.is_known_profile);
|
|
assert_eq!(alt_profile.profile_family, "rt3-105-alt-map-container-v1");
|
|
assert!(alt_profile.is_known_profile);
|
|
|
|
let generic_map_profile = classify_container_profile(
|
|
Some("gmp"),
|
|
Some(&SmpHeaderVariantProbe {
|
|
variant_family: "rt3-map-header-family".to_string(),
|
|
variant_evidence: vec![],
|
|
is_known_family: true,
|
|
}),
|
|
Some(&SmpSecondaryVariantProbe {
|
|
aligned_window_offset: 0,
|
|
words: vec![0x00140000, 0x93e00100, 0x00000004, 0xa0000000],
|
|
hex_words: vec![],
|
|
variant_family: "rt3-map-secondary-family-v1".to_string(),
|
|
variant_evidence: vec![],
|
|
}),
|
|
)
|
|
.expect("generic map profile");
|
|
|
|
assert_eq!(
|
|
generic_map_profile.profile_family,
|
|
"rt3-map-container-family"
|
|
);
|
|
assert!(generic_map_profile.is_known_profile);
|
|
}
|
|
|
|
fn empty_analysis_report() -> SmpSaveCompanyChairmanAnalysisReport {
|
|
SmpSaveCompanyChairmanAnalysisReport {
|
|
profile_family: "rt3-105-scenario-save-container-v1".to_string(),
|
|
selected_company_id: None,
|
|
selected_chairman_profile_id: None,
|
|
world_selection_context: None,
|
|
world_issue_37: None,
|
|
world_economic_tuning: None,
|
|
world_finance_neighborhood: None,
|
|
train_collection_header: None,
|
|
train_collection_directory: None,
|
|
region_collection_header: None,
|
|
region_record_triplets: None,
|
|
region_queued_notice_records: None,
|
|
region_fixed_row_run_candidates: None,
|
|
placed_structure_collection_header: None,
|
|
placed_structure_record_triplets: None,
|
|
placed_structure_dynamic_side_buffer: None,
|
|
placed_structure_dynamic_side_buffer_alignment: None,
|
|
unclassified_tagged_collection_headers: Vec::new(),
|
|
company_entries: Vec::new(),
|
|
chairman_entries: Vec::new(),
|
|
notes: Vec::new(),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn compares_region_fixed_row_run_candidates_by_shape_signature() {
|
|
let mut left = empty_analysis_report();
|
|
left.region_fixed_row_run_candidates = Some(SmpSaveRegionFixedRowRunCandidateProbe {
|
|
profile_family: left.profile_family.clone(),
|
|
source_kind: "save-region-fixed-row-run-candidates".to_string(),
|
|
semantic_family: "scenario-save-region-fixed-row-run-candidates".to_string(),
|
|
target_row_count: 145,
|
|
target_row_stride: 0x29,
|
|
target_row_stride_hex: "0x29".to_string(),
|
|
scan_start_offset: 0,
|
|
scan_start_offset_hex: "0x0".to_string(),
|
|
scan_end_offset: 0x100,
|
|
scan_end_offset_hex: "0x100".to_string(),
|
|
candidates: vec![
|
|
SmpSaveRegionFixedRowRunCandidate {
|
|
count_offset: 0x20,
|
|
count_offset_hex: "0x20".to_string(),
|
|
row_count: 145,
|
|
row_stride: 0x29,
|
|
row_stride_hex: "0x29".to_string(),
|
|
rows_offset: 0x24,
|
|
rows_offset_hex: "0x24".to_string(),
|
|
rows_end_offset: 0x39,
|
|
rows_end_offset_hex: "0x39".to_string(),
|
|
distance_to_region_metadata_tag: 0xc7,
|
|
distance_to_region_metadata_tag_hex: "0xc7".to_string(),
|
|
dword_lane_summaries: Vec::new(),
|
|
shape_signature: "pf32=[0x14:120]|small=[0x20:17]|zero=[0x20:11]|trail=28/63"
|
|
.to_string(),
|
|
shape_family_signature:
|
|
"dense_pf32=[0x14]|small_nonzero=[0x20]|partial_zero=[0x20]|trail_bucket=3/7"
|
|
.to_string(),
|
|
trailing_byte_zero_count: 28,
|
|
trailing_byte_nonzero_count: 117,
|
|
trailing_byte_distinct_value_count: 63,
|
|
trailing_byte_sample_values_hex: Vec::new(),
|
|
best_probable_density_lane_relative_offset_hex: Some("0x14".to_string()),
|
|
},
|
|
SmpSaveRegionFixedRowRunCandidate {
|
|
count_offset: 0x40,
|
|
count_offset_hex: "0x40".to_string(),
|
|
row_count: 145,
|
|
row_stride: 0x29,
|
|
row_stride_hex: "0x29".to_string(),
|
|
rows_offset: 0x44,
|
|
rows_offset_hex: "0x44".to_string(),
|
|
rows_end_offset: 0x59,
|
|
rows_end_offset_hex: "0x59".to_string(),
|
|
distance_to_region_metadata_tag: 0xa7,
|
|
distance_to_region_metadata_tag_hex: "0xa7".to_string(),
|
|
dword_lane_summaries: Vec::new(),
|
|
shape_signature: "pf32=[0x18:107]|small=[0x10:17]|zero=[0x14:12]|trail=32/58"
|
|
.to_string(),
|
|
shape_family_signature:
|
|
"dense_pf32=[]|small_nonzero=[0x10]|partial_zero=[0x14]|trail_bucket=4/7"
|
|
.to_string(),
|
|
trailing_byte_zero_count: 32,
|
|
trailing_byte_nonzero_count: 113,
|
|
trailing_byte_distinct_value_count: 58,
|
|
trailing_byte_sample_values_hex: Vec::new(),
|
|
best_probable_density_lane_relative_offset_hex: Some("0x18".to_string()),
|
|
},
|
|
],
|
|
evidence: Vec::new(),
|
|
});
|
|
|
|
let mut right = empty_analysis_report();
|
|
right.region_fixed_row_run_candidates = Some(SmpSaveRegionFixedRowRunCandidateProbe {
|
|
profile_family: right.profile_family.clone(),
|
|
source_kind: "save-region-fixed-row-run-candidates".to_string(),
|
|
semantic_family: "scenario-save-region-fixed-row-run-candidates".to_string(),
|
|
target_row_count: 145,
|
|
target_row_stride: 0x29,
|
|
target_row_stride_hex: "0x29".to_string(),
|
|
scan_start_offset: 0,
|
|
scan_start_offset_hex: "0x0".to_string(),
|
|
scan_end_offset: 0x100,
|
|
scan_end_offset_hex: "0x100".to_string(),
|
|
candidates: vec![
|
|
SmpSaveRegionFixedRowRunCandidate {
|
|
count_offset: 0x80,
|
|
count_offset_hex: "0x80".to_string(),
|
|
row_count: 145,
|
|
row_stride: 0x29,
|
|
row_stride_hex: "0x29".to_string(),
|
|
rows_offset: 0x84,
|
|
rows_offset_hex: "0x84".to_string(),
|
|
rows_end_offset: 0x99,
|
|
rows_end_offset_hex: "0x99".to_string(),
|
|
distance_to_region_metadata_tag: 0x67,
|
|
distance_to_region_metadata_tag_hex: "0x67".to_string(),
|
|
dword_lane_summaries: Vec::new(),
|
|
shape_signature: "pf32=[0x18:107]|small=[0x10:17]|zero=[0x14:12]|trail=32/58"
|
|
.to_string(),
|
|
shape_family_signature:
|
|
"dense_pf32=[]|small_nonzero=[0x10]|partial_zero=[0x14]|trail_bucket=4/7"
|
|
.to_string(),
|
|
trailing_byte_zero_count: 32,
|
|
trailing_byte_nonzero_count: 113,
|
|
trailing_byte_distinct_value_count: 58,
|
|
trailing_byte_sample_values_hex: Vec::new(),
|
|
best_probable_density_lane_relative_offset_hex: Some("0x18".to_string()),
|
|
},
|
|
SmpSaveRegionFixedRowRunCandidate {
|
|
count_offset: 0xa0,
|
|
count_offset_hex: "0xa0".to_string(),
|
|
row_count: 145,
|
|
row_stride: 0x29,
|
|
row_stride_hex: "0x29".to_string(),
|
|
rows_offset: 0xa4,
|
|
rows_offset_hex: "0xa4".to_string(),
|
|
rows_end_offset: 0xb9,
|
|
rows_end_offset_hex: "0xb9".to_string(),
|
|
distance_to_region_metadata_tag: 0x47,
|
|
distance_to_region_metadata_tag_hex: "0x47".to_string(),
|
|
dword_lane_summaries: Vec::new(),
|
|
shape_signature: "pf32=[0x24:100]|small=[0xc:16]|zero=[0xc:11]|trail=34/60"
|
|
.to_string(),
|
|
shape_family_signature:
|
|
"dense_pf32=[]|small_nonzero=[0xc]|partial_zero=[0xc]|trail_bucket=4/7"
|
|
.to_string(),
|
|
trailing_byte_zero_count: 34,
|
|
trailing_byte_nonzero_count: 111,
|
|
trailing_byte_distinct_value_count: 60,
|
|
trailing_byte_sample_values_hex: Vec::new(),
|
|
best_probable_density_lane_relative_offset_hex: Some("0x24".to_string()),
|
|
},
|
|
],
|
|
evidence: Vec::new(),
|
|
});
|
|
|
|
let comparison = compare_save_region_fixed_row_run_candidates(&left, &right)
|
|
.expect("comparison should build");
|
|
assert_eq!(comparison.shared_shape_matches.len(), 1);
|
|
assert_eq!(
|
|
comparison.shared_shape_matches[0].shape_signature,
|
|
"pf32=[0x18:107]|small=[0x10:17]|zero=[0x14:12]|trail=32/58"
|
|
);
|
|
assert_eq!(comparison.shared_shape_matches[0].left_rank, 2);
|
|
assert_eq!(comparison.shared_shape_matches[0].right_rank, 1);
|
|
assert_eq!(comparison.shared_shape_family_matches.len(), 1);
|
|
assert_eq!(
|
|
comparison.shared_shape_family_matches[0].shape_signature,
|
|
"dense_pf32=[]|small_nonzero=[0x10]|partial_zero=[0x14]|trail_bucket=4/7"
|
|
);
|
|
assert_eq!(comparison.left_only_shape_signatures.len(), 1);
|
|
assert_eq!(comparison.right_only_shape_signatures.len(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn builds_region_service_trace_report_with_explicit_latch_blockers() {
|
|
let mut analysis = empty_analysis_report();
|
|
analysis.region_record_triplets = Some(SmpSaveRegionRecordTripletProbe {
|
|
profile_family: analysis.profile_family.clone(),
|
|
source_kind: "save-region-record-triplets".to_string(),
|
|
semantic_family: "marker09".to_string(),
|
|
records_tag_offset: 0,
|
|
close_tag_offset: 0,
|
|
record_count: 1,
|
|
entries: vec![SmpSaveRegionRecordTripletEntryProbe {
|
|
record_index: 0,
|
|
name: "Marker09".to_string(),
|
|
record_payload_relative_offset: 0,
|
|
record_payload_relative_offset_hex: "0x0".to_string(),
|
|
name_tag_relative_offset: 0,
|
|
policy_tag_relative_offset: 0,
|
|
profile_tag_relative_offset: 0,
|
|
pre_name_prefix_len: 0,
|
|
pre_name_prefix_hex_bytes: Vec::new(),
|
|
pre_name_prefix_dword_candidates: Vec::new(),
|
|
policy_chunk_len: 0,
|
|
profile_chunk_len: 0,
|
|
policy_leading_f32_0: 368.0,
|
|
policy_leading_f32_1: 0.0,
|
|
policy_leading_f32_2: 92.0,
|
|
policy_reserved_dwords: Vec::new(),
|
|
policy_reserved_dword_candidates: Vec::new(),
|
|
policy_trailing_word: 0,
|
|
policy_trailing_word_hex: "0x0000".to_string(),
|
|
profile_collection: Some(SmpSaveRegionProfileCollectionProbe {
|
|
direct_collection_flag: 1,
|
|
entry_stride: 0x22,
|
|
live_id_bound: 17,
|
|
live_record_count: 17,
|
|
entry_start_relative_offset: 0,
|
|
trailing_padding_len: 0,
|
|
entries: Vec::new(),
|
|
}),
|
|
}],
|
|
evidence: Vec::new(),
|
|
});
|
|
|
|
let trace = build_region_service_trace_report(&analysis);
|
|
assert_eq!(trace.region_record_triplet_count, 1);
|
|
assert_eq!(trace.queued_notice_record_count, 0);
|
|
assert!(!trace.atlas_candidate_consumers.is_empty());
|
|
assert_eq!(trace.known_owner_bridge_fields.len(), 6);
|
|
assert_eq!(trace.known_bridge_helpers.len(), 14);
|
|
assert_eq!(trace.next_owner_questions.len(), 5);
|
|
assert_eq!(trace.candidate_consumer_hypotheses.len(), 6);
|
|
assert_eq!(
|
|
trace.candidate_consumer_hypotheses[0].status,
|
|
"highest_priority_static_mapping_target"
|
|
);
|
|
assert_eq!(
|
|
trace.candidate_consumer_hypotheses[2].status,
|
|
"secondary_candidate_after_pending_service"
|
|
);
|
|
assert_eq!(
|
|
trace.candidate_consumer_hypotheses[3].status,
|
|
"next_global_restore_handoff_target"
|
|
);
|
|
assert_eq!(
|
|
trace.candidate_consumer_hypotheses[1].status,
|
|
"parallel_static_mapping_target"
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[1]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x5209")
|
|
&& line.contains("0x520a")
|
|
&& line.contains("0x520b"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[1]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x0041f5c0")
|
|
&& line.contains("[region+0x37f]")
|
|
&& line.contains("[region+0x385]"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[1]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00444887")
|
|
&& line.contains("0x00487c20")
|
|
&& line.contains("0x0040b5d0"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[1]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00421ce0") && line.contains("0x0041fb00"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[1]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00421730")
|
|
&& line.contains("[region+0x242/+0x246/+0x24a/+0x24e/+0x252]"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[1]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00448740..0x0044881f")
|
|
&& line.contains("0x006cfc9c")
|
|
&& line.contains("0x53b070")
|
|
&& line.contains("0x00487bd0"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[1]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00487de0")
|
|
&& line.contains("0x00533cf0")
|
|
&& line.contains("0x00536ea0"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[1]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x0044c4b0")
|
|
&& line.contains("0x00455f60")
|
|
&& line.contains("bit 0x10"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[3]
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x00444887"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[3]
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x00487c20"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[3]
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x0040b5d0"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[3]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00444887") && line.contains("0x00421510"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[3]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00444b90") && line.contains("0x00420560"))
|
|
);
|
|
assert_eq!(
|
|
trace.candidate_consumer_hypotheses[4].status,
|
|
"next_post_load_owner_family"
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[4]
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x004384d0"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[4]
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x004133b0"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[4]
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x00421c20"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[4]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00446d40") && line.contains("0x004384d0"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[4]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x004133b0") && line.contains("0x00480710"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[4]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00421c20") && line.contains("0x004235c0"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[1]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x004881b0")
|
|
&& line.contains("[region+0x3d]")
|
|
&& line.contains("[region+0x41]"))
|
|
);
|
|
assert_eq!(trace.entries.len(), 1);
|
|
assert_eq!(
|
|
trace.entries[0].branches[0].status,
|
|
"blocked_missing_pending_bonus_owner_lane"
|
|
);
|
|
assert_eq!(
|
|
trace.entries[0].branches[1].status,
|
|
"blocked_missing_completion_and_one_shot_latches"
|
|
);
|
|
assert!(
|
|
trace
|
|
.notes
|
|
.iter()
|
|
.any(|line| { line.contains("pre-name prefix lengths") && line.contains("[0]") })
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn builds_periodic_company_service_trace_report_with_candidate_consumers() {
|
|
let mut analysis = empty_analysis_report();
|
|
analysis.selected_company_id = Some(7);
|
|
analysis.placed_structure_record_triplets =
|
|
Some(SmpSavePlacedStructureRecordTripletProbe {
|
|
profile_family: analysis.profile_family.clone(),
|
|
source_kind: "save-placed-structure-record-triplets".to_string(),
|
|
semantic_family: "scenario-save-placed-structure-record-triplets".to_string(),
|
|
records_tag_offset: 0,
|
|
close_tag_offset: 0,
|
|
record_count: 2,
|
|
entries: vec![
|
|
SmpSavePlacedStructureRecordTripletEntryProbe {
|
|
record_index: 0,
|
|
primary_name: "StationA".to_string(),
|
|
secondary_name: "StationSetA".to_string(),
|
|
name_tag_relative_offset: 0,
|
|
policy_tag_relative_offset: 0,
|
|
profile_tag_relative_offset: 0,
|
|
policy_chunk_len: 0x1a,
|
|
profile_chunk_len: 0x20,
|
|
policy_f32_lane_0: 0.0,
|
|
policy_f32_lane_1: 0.0,
|
|
policy_f32_lane_2: 0.0,
|
|
policy_f32_lane_3: 0.0,
|
|
policy_f32_lane_4: 0.0,
|
|
policy_reserved_dword: 0,
|
|
policy_trailing_word: 0x0101,
|
|
policy_trailing_word_hex: "0x0101".to_string(),
|
|
profile_open_marker: 0x5dc1,
|
|
profile_open_marker_hex: "0x00005dc1".to_string(),
|
|
profile_repeated_primary_name: "StationA".to_string(),
|
|
profile_repeated_secondary_name: "StationSetA".to_string(),
|
|
profile_footer_relative_offset: 0,
|
|
profile_footer_relative_offset_hex: "0x0".to_string(),
|
|
profile_pre_footer_padding_len: 1,
|
|
profile_pre_footer_padding_hex_bytes: vec!["0x01".to_string()],
|
|
profile_companion_byte_u8: Some(1),
|
|
profile_companion_byte_hex: Some("0x01".to_string()),
|
|
profile_payload_dword: 0x0e373880,
|
|
profile_payload_dword_hex: "0x0e373880".to_string(),
|
|
profile_sentinel_i32: -1,
|
|
profile_status_kind: "unset".to_string(),
|
|
farm_growth_stage_index: None,
|
|
profile_close_marker: 0x5dc2,
|
|
profile_close_marker_hex: "0x00005dc2".to_string(),
|
|
},
|
|
SmpSavePlacedStructureRecordTripletEntryProbe {
|
|
record_index: 1,
|
|
primary_name: "StationB".to_string(),
|
|
secondary_name: "StationSetB".to_string(),
|
|
name_tag_relative_offset: 0,
|
|
policy_tag_relative_offset: 0,
|
|
profile_tag_relative_offset: 0,
|
|
policy_chunk_len: 0x1a,
|
|
profile_chunk_len: 0x20,
|
|
policy_f32_lane_0: 0.0,
|
|
policy_f32_lane_1: 0.0,
|
|
policy_f32_lane_2: 0.0,
|
|
policy_f32_lane_3: 0.0,
|
|
policy_f32_lane_4: 0.0,
|
|
policy_reserved_dword: 0,
|
|
policy_trailing_word: 0x0101,
|
|
policy_trailing_word_hex: "0x0101".to_string(),
|
|
profile_open_marker: 0x5dc1,
|
|
profile_open_marker_hex: "0x00005dc1".to_string(),
|
|
profile_repeated_primary_name: "StationB".to_string(),
|
|
profile_repeated_secondary_name: "StationSetB".to_string(),
|
|
profile_footer_relative_offset: 0,
|
|
profile_footer_relative_offset_hex: "0x0".to_string(),
|
|
profile_pre_footer_padding_len: 1,
|
|
profile_pre_footer_padding_hex_bytes: vec!["0x00".to_string()],
|
|
profile_companion_byte_u8: Some(0),
|
|
profile_companion_byte_hex: Some("0x00".to_string()),
|
|
profile_payload_dword: 0x0e373500,
|
|
profile_payload_dword_hex: "0x0e373500".to_string(),
|
|
profile_sentinel_i32: -1,
|
|
profile_status_kind: "unset".to_string(),
|
|
farm_growth_stage_index: None,
|
|
profile_close_marker: 0x5dc2,
|
|
profile_close_marker_hex: "0x00005dc2".to_string(),
|
|
},
|
|
],
|
|
evidence: Vec::new(),
|
|
});
|
|
analysis.region_fixed_row_run_candidates = Some(SmpSaveRegionFixedRowRunCandidateProbe {
|
|
profile_family: analysis.profile_family.clone(),
|
|
source_kind: "save-region-fixed-row-run-candidates".to_string(),
|
|
semantic_family: "scenario-save-region-fixed-row-run-candidates".to_string(),
|
|
target_row_count: 4,
|
|
target_row_stride: 0xbc,
|
|
target_row_stride_hex: "0xbc".to_string(),
|
|
scan_start_offset: 0,
|
|
scan_start_offset_hex: "0x0".to_string(),
|
|
scan_end_offset: 0x2000,
|
|
scan_end_offset_hex: "0x2000".to_string(),
|
|
candidates: vec![SmpSaveRegionFixedRowRunCandidate {
|
|
count_offset: 0x40,
|
|
count_offset_hex: "0x40".to_string(),
|
|
row_count: 4,
|
|
row_stride: 0xbc,
|
|
row_stride_hex: "0xbc".to_string(),
|
|
rows_offset: 0x5310,
|
|
rows_offset_hex: "0x5310".to_string(),
|
|
rows_end_offset: 0x5600,
|
|
rows_end_offset_hex: "0x5600".to_string(),
|
|
distance_to_region_metadata_tag: 0x80,
|
|
distance_to_region_metadata_tag_hex: "0x80".to_string(),
|
|
dword_lane_summaries: Vec::new(),
|
|
shape_signature: "shape-a".to_string(),
|
|
shape_family_signature: "family-a".to_string(),
|
|
trailing_byte_zero_count: 4,
|
|
trailing_byte_nonzero_count: 0,
|
|
trailing_byte_distinct_value_count: 1,
|
|
trailing_byte_sample_values_hex: vec!["0x00".to_string()],
|
|
best_probable_density_lane_relative_offset_hex: Some("0x24".to_string()),
|
|
}],
|
|
evidence: Vec::new(),
|
|
});
|
|
analysis
|
|
.company_entries
|
|
.push(SmpSaveCompanyRecordAnalysisEntry {
|
|
company_id: 7,
|
|
name: "Test Company".to_string(),
|
|
active: true,
|
|
linked_chairman_profile_id: Some(3),
|
|
outstanding_shares: 1000,
|
|
debt: 0,
|
|
bond_count: 0,
|
|
live_bond_slots: Vec::new(),
|
|
largest_live_bond_principal: None,
|
|
highest_coupon_live_bond_principal: None,
|
|
available_track_laying_capacity: Some(5),
|
|
company_value_scalar_f32: 1.0,
|
|
cached_share_support_scalar_f32: 1.0,
|
|
cached_share_price_f32: 1.0,
|
|
chairman_salary_baseline: 0,
|
|
chairman_salary_current: 0,
|
|
chairman_bonus_year: 0,
|
|
chairman_bonus_amount: 0,
|
|
founding_year: 1900,
|
|
last_bankruptcy_year: 0,
|
|
last_dividend_year: 0,
|
|
preferred_locomotive_engine_type_raw_u8: 2,
|
|
preferred_locomotive_engine_type_raw_hex: "0x02".to_string(),
|
|
city_connection_latch: true,
|
|
linked_transit_latch: false,
|
|
linked_transit_autoroute_site_score_cache_refresh_absolute_counter: 0x31380,
|
|
linked_transit_site_peer_cache_refresh_absolute_counter: 0x7ff80,
|
|
linked_transit_route_anchor_entry_id: Some(77),
|
|
linked_transit_route_anchor_fallback_counts: vec![3, 5, 8],
|
|
merger_cooldown_year: 0,
|
|
takeover_cooldown_year: 0,
|
|
scalar_dword_candidates: Vec::new(),
|
|
post_capacity_dword_candidates: Vec::new(),
|
|
stat_band_root_0cfb_candidates: Vec::new(),
|
|
stat_band_root_0d7f_candidates: Vec::new(),
|
|
stat_band_root_1c47_candidates: Vec::new(),
|
|
});
|
|
|
|
let trace = build_periodic_company_service_trace_report(&analysis);
|
|
assert_eq!(trace.selected_company_id, Some(7));
|
|
assert_eq!(trace.atlas_candidate_consumers.len(), 9);
|
|
assert_eq!(trace.known_bridge_helpers.len(), 84);
|
|
assert_eq!(trace.next_owner_questions.len(), 5);
|
|
assert_eq!(
|
|
trace.linked_transit_shellless_readiness_status,
|
|
"timed_cache_and_train_side_followons_grounded_site_cache_input_owners_missing"
|
|
);
|
|
assert_eq!(
|
|
trace.linked_transit_minimum_persisted_identity_inputs.len(),
|
|
5
|
|
);
|
|
assert_eq!(trace.linked_transit_live_rebuilt_cache_lanes.len(), 5);
|
|
assert_eq!(trace.linked_transit_runtime_backed_input_families.len(), 18);
|
|
assert_eq!(trace.linked_transit_remaining_owner_gaps.len(), 2);
|
|
assert_eq!(trace.companies.len(), 1);
|
|
assert_eq!(
|
|
trace.companies[0].linked_transit_autoroute_site_score_cache_refresh_absolute_counter,
|
|
0x31380
|
|
);
|
|
assert_eq!(
|
|
trace.companies[0].linked_transit_site_peer_cache_refresh_absolute_counter,
|
|
0x7ff80
|
|
);
|
|
assert_eq!(
|
|
trace.peer_site_selector_candidate_owner_strip,
|
|
"0x0045c150 -> 0x0045c310 -> 0x0045c36e -> 0x00456100 -> 0x00455b70"
|
|
);
|
|
assert_eq!(
|
|
trace.peer_site_selector_candidate_persisted_tag_hex,
|
|
"0x5dc1"
|
|
);
|
|
assert_eq!(
|
|
trace.peer_site_selector_candidate_selector_lane,
|
|
"[owner+0x23e]"
|
|
);
|
|
assert_eq!(
|
|
trace.peer_site_selector_candidate_secondary_payload_lane,
|
|
"[owner+0x242]"
|
|
);
|
|
assert!(
|
|
trace
|
|
.peer_site_selector_candidate_post_secondary_byte_status
|
|
.contains("post-secondary discriminator byte")
|
|
);
|
|
assert_eq!(
|
|
trace.peer_site_selector_candidate_class_identity_status,
|
|
"grounded_direct_local_helper_strip"
|
|
);
|
|
assert_eq!(trace.peer_site_selector_candidate_helper_linkage.len(), 4);
|
|
assert_eq!(
|
|
trace
|
|
.peer_site_selector_candidate_saved_payload_summaries
|
|
.len(),
|
|
2
|
|
);
|
|
assert_eq!(
|
|
trace.peer_site_selector_candidate_saved_payload_summaries[0].profile_payload_dword_hex,
|
|
"0x0e373500"
|
|
);
|
|
assert_eq!(
|
|
trace.peer_site_selector_candidate_saved_payload_summaries[0].count,
|
|
1
|
|
);
|
|
assert_eq!(
|
|
trace.peer_site_selector_candidate_saved_payload_delta_summaries[0].delta_hex,
|
|
"0x00000380"
|
|
);
|
|
assert_eq!(
|
|
trace.peer_site_selector_candidate_saved_footer_padding_summaries[0].padding_len,
|
|
1
|
|
);
|
|
assert_eq!(
|
|
trace.peer_site_selector_candidate_saved_companion_byte_summaries[0].companion_byte_hex,
|
|
"0x00"
|
|
);
|
|
assert_eq!(
|
|
trace.peer_site_selector_candidate_saved_policy_trailing_word_summaries[0]
|
|
.policy_trailing_word_hex,
|
|
"0x0101"
|
|
);
|
|
assert_eq!(
|
|
trace.peer_site_selector_candidate_saved_policy_trailing_word_summaries[0].count,
|
|
2
|
|
);
|
|
assert_eq!(
|
|
trace.peer_site_selector_candidate_saved_nonzero_companion_name_pair_summaries[0]
|
|
.primary_name,
|
|
"StationA"
|
|
);
|
|
assert_eq!(trace.peer_site_persisted_selector_bundle_fields.len(), 4);
|
|
assert_eq!(trace.peer_site_rebuilt_transient_followon_fields.len(), 4);
|
|
assert_eq!(
|
|
trace.peer_site_shellless_minimum_persisted_identity_status,
|
|
"name_pair_and_post_secondary_byte_minimum_identity_subset_child_runtime_bundle_rebuild_followon"
|
|
);
|
|
assert_eq!(
|
|
trace
|
|
.peer_site_shellless_minimum_persisted_identity_inputs
|
|
.len(),
|
|
5
|
|
);
|
|
assert_eq!(trace.peer_site_restore_input_fields.len(), 5);
|
|
assert_eq!(trace.peer_site_runtime_input_fields.len(), 3);
|
|
assert_eq!(
|
|
trace.peer_site_runtime_reconstruction_status,
|
|
"restore_subset_and_bring_up_reconstruct_runtime_subset"
|
|
);
|
|
assert_eq!(trace.peer_site_runtime_reconstruction_steps.len(), 4);
|
|
assert_eq!(trace.near_city_acquisition_region_input_fields.len(), 5);
|
|
assert_eq!(trace.near_city_acquisition_peer_input_fields.len(), 7);
|
|
assert_eq!(trace.near_city_acquisition_company_input_fields.len(), 6);
|
|
assert_eq!(
|
|
trace.near_city_acquisition_shellless_readiness_status,
|
|
"peer_and_company_inputs_grounded_site_owner_and_tri_restore_gaps_remaining"
|
|
);
|
|
assert_eq!(
|
|
trace.near_city_acquisition_site_owner_company_projection_status,
|
|
"ordinary_replay_ruled_down_stream_load_callback_grounded_tuple_finalize_path_grounded_nontransport_restore_source_missing"
|
|
);
|
|
assert_eq!(
|
|
trace.near_city_acquisition_site_self_id_projection_status,
|
|
"live_meaning_grounded_reconstructible_from_collection_identity"
|
|
);
|
|
assert_eq!(
|
|
trace.near_city_acquisition_site_cached_tri_lane_projection_status,
|
|
"live_writer_family_grounded_semantics_and_persisted_inputs_missing"
|
|
);
|
|
assert_eq!(
|
|
trace.near_city_acquisition_nontransport_persisted_source_status,
|
|
"ordinary_runtime_effect_candidate_present_trigger_lane_mapping_missing"
|
|
);
|
|
assert_eq!(
|
|
trace
|
|
.near_city_acquisition_nontransport_persisted_source_candidates
|
|
.len(),
|
|
5
|
|
);
|
|
assert_eq!(
|
|
trace.near_city_acquisition_tri_lane_save_shape_family_status,
|
|
"save_shape_family_candidates_present_fixed_offset_ruled_down"
|
|
);
|
|
assert_eq!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_save_shape_family_candidates
|
|
.len(),
|
|
1
|
|
);
|
|
assert_eq!(
|
|
trace.near_city_acquisition_tri_lane_save_shape_family_candidates[0]
|
|
.shape_family_signature,
|
|
"family-a"
|
|
);
|
|
assert_eq!(
|
|
trace.near_city_acquisition_tri_lane_live_service_status,
|
|
"candidate_gate_and_live_writer_family_grounded_exact_formula_and_persisted_inputs_missing"
|
|
);
|
|
assert_eq!(
|
|
trace.near_city_acquisition_candidate_subtype_projection_status,
|
|
"cached_candidate_id_bridge_grounded_via_stream_load"
|
|
);
|
|
assert_eq!(
|
|
trace.near_city_acquisition_backing_record_projection_status,
|
|
"stream_load_callback_grounded_via_0x40ce60"
|
|
);
|
|
assert_eq!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_live_owner_families
|
|
.len(),
|
|
5
|
|
);
|
|
assert_eq!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_candidate_gate_fields
|
|
.len(),
|
|
5
|
|
);
|
|
assert_eq!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_runtime_writer_roles
|
|
.len(),
|
|
5
|
|
);
|
|
assert_eq!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_direct_caller_families
|
|
.len(),
|
|
5
|
|
);
|
|
assert_eq!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_formula_input_lanes
|
|
.len(),
|
|
5
|
|
);
|
|
assert_eq!(trace.near_city_acquisition_projection_hypotheses.len(), 3);
|
|
assert_eq!(
|
|
trace.near_city_acquisition_projection_hypotheses[0].label,
|
|
"site_owner_replay_from_post_load_refresh_self_id_reconstructible"
|
|
);
|
|
assert_eq!(
|
|
trace.near_city_acquisition_projection_hypotheses[0].status,
|
|
"ordinary_replay_and_stream_load_ruled_down_tuple_finalize_positive_path_grounded"
|
|
);
|
|
assert_eq!(
|
|
trace.near_city_acquisition_projection_hypotheses[1].label,
|
|
"site_cached_tri_lane_payload_or_restore_owner"
|
|
);
|
|
assert_eq!(
|
|
trace.near_city_acquisition_projection_hypotheses[1].status,
|
|
"checked_in_save_seams_ruled_down_live_scoring_family_grounded_exact_semantics_open"
|
|
);
|
|
assert_eq!(
|
|
trace.near_city_acquisition_projection_hypotheses[2].label,
|
|
"cached_source_candidate_id_to_subtype_projection"
|
|
);
|
|
assert_eq!(
|
|
trace.near_city_acquisition_projection_hypotheses[2].status,
|
|
"grounded_stream_load_callback_0x40ce60"
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x004133b0"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x004134d0") && line.contains("0x0040f6d0"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x00403ef3 / 0x00404489"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x0046f073 / 0x004707ff"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00444690 -> 0x004133b0")
|
|
&& line.contains("ordinary bring-up replay family"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00444467")
|
|
&& line.contains("0x00413280")
|
|
&& line.contains("0x004444d8")
|
|
&& line.contains("0x00481210")
|
|
&& line.contains("0x00444690"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("after 0x00444690 -> 0x004133b0")
|
|
&& line.contains("0x004134d0 / 0x0040f6d0 / 0x0040ef10"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("[site+0x27a]")
|
|
&& line.contains("0x0042125d")
|
|
&& line.contains("0x0040f793")
|
|
&& line.contains("0x0040dfec")
|
|
&& line.contains("0x004269e4")
|
|
&& line.contains("0x00426a44..0x00426a90")
|
|
&& line.contains("0x00426ad8"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x0040ee10")
|
|
&& line.contains("[site+0x3cc]")
|
|
&& line.contains("0x0040e360..0x0040edf6")
|
|
&& line.contains("[site+0x2a8]")
|
|
&& line.contains("[site+0x2a4]")
|
|
&& line.contains("[site+0x276]")
|
|
&& line.contains("0x00480710")
|
|
&& line.contains("0x00426b10")
|
|
&& line.contains("0x00455860")
|
|
&& line.contains("reads/queries"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x004134d0")
|
|
&& line.contains("0x00518900")
|
|
&& line.contains("0x0040f6d0")
|
|
&& line.contains("[site+0x2a4]")
|
|
&& line.contains("[site+0x276]"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x0040f6d0")
|
|
&& line.contains("[site+0x2a8/+0x272/+0x27a/+0x29e]")
|
|
&& line.contains("[site+0x3d4/+0x3d5]"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00403ef3 / 0x00404489")
|
|
&& line.contains("0x0046f073 / 0x004707ff")
|
|
&& line.contains("0x0040ef10"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("[+0x00/+0x04/+0x0c]")
|
|
&& line.contains("0x0040ef1c")
|
|
&& line.contains("0x0040f5d4")
|
|
&& line.contains("[site+0x276]")
|
|
&& line.contains("[site+0x27a]"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x004707ff")
|
|
&& line.contains("0x004706b0")
|
|
&& line.contains("selector-0x13")
|
|
&& line.contains(
|
|
"0x004197e0 / 0x004134d0 / 0x0040eba0 / 0x0052eb90 / 0x0040ef10"
|
|
))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00472b40")
|
|
&& line.contains("selector-0x72")
|
|
&& line.contains("0x00472bef / 0x00472d03"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00422bb4")
|
|
&& line.contains("0x0062b2fc")
|
|
&& line.contains("literal flags 1/0")
|
|
&& line.contains("out-param"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00508fd1 / 0x005098eb")
|
|
&& line.contains("[this+0x7c]")
|
|
&& line.contains("vtable slot +0x58 plus 0x00507cf0")
|
|
&& line.contains("arg3 forced to zero"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00473c20")
|
|
&& line.contains("0x006ce808..0x006ce988")
|
|
&& line.contains("0x00473c98")
|
|
&& line.contains("queued id slot"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x0042128d")
|
|
&& line.contains("0x00422305")
|
|
&& line.contains("0x004269c9/0x00426a2a")
|
|
&& line.contains("0x004282a9 / 0x004300d6"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00413440")
|
|
&& line.contains("0x36b1 / 0x36b2 / 0x36b3")
|
|
&& line.contains("save side")
|
|
&& line.contains("slot +0x44"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00413280")
|
|
&& line.contains("slot +0x40")
|
|
&& line.contains("0x0040ce60"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x0040f047")
|
|
&& line.contains("0x0040f5d4")
|
|
&& line.contains("[site+0x27a]"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[1]
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x36b1/0x36b2/0x36b3"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[1]
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x0040d450"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[1]
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x00410b30..0x004118f4") && line.contains("0x00412560"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[1]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00481430 -> 0x0047d8e0"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[1]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x0040c9a0"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[1]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x0040a3a1..0x0040a4d3")
|
|
&& line.contains("0x0040fcc0..0x0040fe28")
|
|
&& line.contains("0x00422c62..0x00422d3c"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[1]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x0040d4aa/0x0040d4b0")
|
|
&& line.contains("0x0041114a7/0x004111572")
|
|
&& line.contains("0x0041118aa/0x0041118f4"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[1]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x0040d450")
|
|
&& line.contains("[site+0x276]")
|
|
&& line.contains("0x00436590")
|
|
&& line.contains("[site+0x310]"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[1]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00410b30..0x004118f4")
|
|
&& line.contains("0xbc-stride")
|
|
&& line.contains("[site+0x310/+0x338/+0x360]"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[1]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00412560")
|
|
&& line.contains("0x006cec78")
|
|
&& line.contains("0x0062ba8c"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[2]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00413280") && line.contains("0x0040ce60"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[2]
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x0040cd70 cached source/candidate resolver"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.blockers
|
|
.iter()
|
|
.any(|line| line.contains("0x00413440")
|
|
&& line.contains("0x36b1/0x36b2/0x36b3")
|
|
&& line.contains("load-save strip"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.blockers
|
|
.iter()
|
|
.any(|line| line.contains("0x00413280")
|
|
&& line.contains("0x0040ce60")
|
|
&& line.contains("stream-load bridge"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.blockers
|
|
.iter()
|
|
.any(|line| line.contains("0x00481210")
|
|
&& line.contains("0x004133b0")
|
|
&& line.contains("0x0040ef10"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.blockers
|
|
.iter()
|
|
.any(|line| line.contains("0x00431b20")
|
|
&& line.contains("0x00433130")
|
|
&& line.contains("0x0042db20")
|
|
&& line.contains("0x0042e050")
|
|
&& line.contains("0x0062be18")
|
|
&& line.contains("[event+0x7ef]")
|
|
&& line.contains("kind 8"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00433130")
|
|
&& line.contains("0x0062be18")
|
|
&& line.contains("0x4e21/0x4e22")
|
|
&& line.contains("0x004d90ba..0x004d91ed")
|
|
&& line.contains("0x4e98..0x4ea2")
|
|
&& line.contains("0x004d91b3"))
|
|
);
|
|
assert!(
|
|
trace.near_city_acquisition_projection_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x004db02a")
|
|
&& line.contains("0x004db1b8..0x004db309")
|
|
&& line.contains("0x4e84")
|
|
&& line.contains("0x004db9e5..0x004db9f1")
|
|
&& line.contains("0x00432ea0"))
|
|
);
|
|
assert_eq!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.len(),
|
|
25
|
|
);
|
|
assert_eq!(trace.near_city_acquisition_remaining_owner_gaps.len(), 2);
|
|
assert_eq!(trace.near_city_acquisition_region_lane_statuses.len(), 4);
|
|
assert!(trace.atlas_candidate_consumers.iter().any(|line| {
|
|
line.contains("0x00420030 / 0x00420280")
|
|
&& line.contains("0x006cec20")
|
|
&& line.contains("peer-site boolean/selector pair")
|
|
}));
|
|
assert!(trace.atlas_candidate_consumers.iter().any(|line| {
|
|
line.contains("0x0040dc40")
|
|
&& line.contains("linked-site mutation validator/apply owner")
|
|
}));
|
|
assert!(trace.atlas_candidate_consumers.iter().any(|line| {
|
|
line.contains("0x00402cb0") && line.contains("direct-placement builder owner")
|
|
}));
|
|
assert!(trace.next_owner_questions.iter().any(|line| {
|
|
line.contains("repopulates placed-structure owner-company field [site+0x276]")
|
|
&& line.contains("0x00431b20")
|
|
}));
|
|
assert!(trace.next_owner_questions.iter().any(|line| {
|
|
line.contains("0x0040d450 / 0x00410b30..0x004118f4")
|
|
&& line.contains("0x00412560")
|
|
&& line.contains("[site+0x2b4/+0x2b8/+0x2bc]")
|
|
}));
|
|
assert!(
|
|
trace
|
|
.linked_transit_minimum_persisted_identity_inputs
|
|
.iter()
|
|
.any(|line| line.contains("[site+0x276]")
|
|
&& line.contains("[site+0x04]")
|
|
&& line.contains("0x0047efe0 / 0x0047fd50"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_minimum_persisted_identity_inputs
|
|
.iter()
|
|
.any(|line| line.contains("[site+0x2a4]")
|
|
&& line.contains("[site+0x2a8]")
|
|
&& line.contains("[peer+0x04/+0x08]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_live_rebuilt_cache_lanes
|
|
.iter()
|
|
.any(|line| line.contains("0x004093d0")
|
|
&& line.contains("[company+0x0d3e]")
|
|
&& line.contains("+0x02/+0x06/+0x0a"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_live_rebuilt_cache_lanes
|
|
.iter()
|
|
.any(|line| line.contains("0x00407bd0")
|
|
&& line.contains("[site+0x0e/+0x12/+0x16]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_live_rebuilt_cache_lanes
|
|
.iter()
|
|
.any(|line| line.contains("0x00481910")
|
|
&& line.contains("0x004819b0")
|
|
&& line.contains("0x004a9340"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_live_rebuilt_cache_lanes
|
|
.iter()
|
|
.any(|line| line.contains("0x004aee2b")
|
|
&& line.contains("[site+0x5c5]")
|
|
&& line.contains("[world+0x15]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x00429c10")
|
|
&& line.contains("0x004093d0")
|
|
&& line.contains("live company roster"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x00444690")
|
|
&& line.contains("0x004133b0")
|
|
&& line.contains("0x0040ee10")
|
|
&& line.contains("0x00480710")
|
|
&& line.contains("0x004160aa"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("[company+0x0d3e]") && line.contains("0x00409720"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("[company+0x0d3a]") && line.contains("0x00407bd0"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x0062ba8c")
|
|
&& line.contains("0x0041f4e0")
|
|
&& line.contains("0x0041ede0")
|
|
&& line.contains("0x0041e970"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x004a6360")
|
|
&& line.contains("0x004a6630")
|
|
&& line.contains("0x00494fb0"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x00408280") && line.contains("0x00408380"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x00409770") && line.contains("0x00409830"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("[site+0x5bd]")
|
|
&& line.contains("0x00407780")
|
|
&& line.contains("0x004077e0"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("+0x00/+0x01")
|
|
&& line.contains("+0x02")
|
|
&& line.contains("+0x06")
|
|
&& line.contains("+0x0a")
|
|
&& line.contains("+0x0e/+0x12/+0x16"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("[site+0x5c1]")
|
|
&& line.contains("0x00481910")
|
|
&& line.contains("0x004a9340")
|
|
&& line.contains("0x004819b0"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("[site+0x5c5]") && line.contains("0x004aee2b"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_remaining_owner_gaps
|
|
.iter()
|
|
.any(|line| line.contains("0x0040e360..0x0040edf6")
|
|
&& line.contains("[site+0x276]")
|
|
&& line.contains("earlier restore or service owner"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.linked_transit_remaining_owner_gaps
|
|
.iter()
|
|
.any(|line| line.contains("0x006cec20")
|
|
&& line.contains("0x0041f4e0")
|
|
&& line.contains("0x00494fb0"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_region_input_fields
|
|
.iter()
|
|
.any(|line| line.contains("[site+0x276]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_peer_input_fields
|
|
.iter()
|
|
.any(|line| line.contains("[site+0x04]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_peer_input_fields
|
|
.iter()
|
|
.any(|line| line.contains("[cell+0xd4]") && line.contains("[cell+0xd6]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_company_input_fields
|
|
.iter()
|
|
.any(|line| line.contains("0x2329/0x0d"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_company_input_fields
|
|
.iter()
|
|
.any(|line| line.contains("[company+0x0d35]") && line.contains("0x00401860"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("[company+0x0d35]")
|
|
&& line.contains("[company+0x7664/+0x7668/+0x766c]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x004134d0")
|
|
&& line.contains("0x0040f6d0")
|
|
&& line.contains("[site+0x2a4]")
|
|
&& line.contains("[site+0x276]")
|
|
&& line.contains("[site+0x3d4/+0x3d5]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x0040ef10")
|
|
&& line.contains("0x00403ef3 / 0x00404489")
|
|
&& line.contains("0x0046f073 / 0x004707ff"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x0046f073 / 0x004707ff")
|
|
&& line.contains("[+0x00/+0x04/+0x0c]")
|
|
&& line.contains("0x0040ef10")
|
|
&& line.contains("0x0040f5d4"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x004707ff")
|
|
&& line.contains("0x004706b0")
|
|
&& line.contains("selector-0x13"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x00472b40")
|
|
&& line.contains("selector-0x72")
|
|
&& line.contains("0x00472bef / 0x00472d03"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x00422bb4")
|
|
&& line.contains("live args")
|
|
&& line.contains("literal flags 1/0")
|
|
&& line.contains("out-param"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x00508fd1 / 0x005098eb")
|
|
&& line.contains("[this+0x7c]")
|
|
&& line.contains("0x0040eba0")
|
|
&& line.contains("hard zero third arg"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x00473c20")
|
|
&& line.contains("0x006ce808..0x006ce988")
|
|
&& line.contains("0x00473c98")
|
|
&& line.contains("post-create refresh path"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x0042128d")
|
|
&& line.contains("0x00422305")
|
|
&& line.contains("0x004269c9/0x00426a2a")
|
|
&& line.contains("0x004282a9/0x004300d6"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x00413440")
|
|
&& line.contains("0x36b1/0x36b2/0x36b3")
|
|
&& line.contains("slot +0x44"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x0047efe0") && line.contains("[site+0x276]"))
|
|
);
|
|
let linked_transit_branch = trace.companies[0]
|
|
.branches
|
|
.iter()
|
|
.find(|branch| branch.branch_name == "linked_transit_roster_maintenance")
|
|
.expect("linked-transit branch");
|
|
assert_eq!(
|
|
linked_transit_branch.status,
|
|
"blocked_missing_site_cache_input_owner_mapping"
|
|
);
|
|
assert!(
|
|
linked_transit_branch
|
|
.grounded_inputs
|
|
.iter()
|
|
.any(|line| line.contains("[company+0x0d3e]"))
|
|
);
|
|
assert!(
|
|
linked_transit_branch
|
|
.grounded_inputs
|
|
.iter()
|
|
.any(|line| line.contains("[company+0x0d3a]"))
|
|
);
|
|
assert!(
|
|
linked_transit_branch
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x00409720"))
|
|
);
|
|
assert!(
|
|
linked_transit_branch
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x00408f70"))
|
|
);
|
|
assert!(
|
|
linked_transit_branch
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x00408280"))
|
|
);
|
|
assert!(
|
|
linked_transit_branch
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x00408380"))
|
|
);
|
|
assert!(
|
|
linked_transit_branch
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x00409770"))
|
|
);
|
|
assert!(
|
|
linked_transit_branch
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x00409830"))
|
|
);
|
|
assert!(
|
|
linked_transit_branch
|
|
.blocking_inputs
|
|
.iter()
|
|
.any(|line| line.contains("0x00408280 / 0x00408380"))
|
|
);
|
|
assert!(trace.notes.iter().any(|line| {
|
|
line.contains("0x00409720")
|
|
&& line.contains("[company+0x0d3e]")
|
|
&& line.contains("[company+0x0d3a]")
|
|
&& line.contains("0x00409950")
|
|
}));
|
|
assert!(trace.notes.iter().any(|line| {
|
|
line.contains("0x00408280")
|
|
&& line.contains("[site+0x16]")
|
|
&& line.contains("0x00409770")
|
|
&& line.contains("0x00409830")
|
|
}));
|
|
assert!(trace.notes.iter().any(|line| {
|
|
line.contains("0x00481910")
|
|
&& line.contains("0x004819b0")
|
|
&& line.contains("[site+0x5c1]")
|
|
&& line.contains("0x004a9340")
|
|
&& line.contains("0x004aee2b")
|
|
&& line.contains("[site+0x5c5]")
|
|
}));
|
|
assert!(trace.notes.iter().any(|line| {
|
|
line.contains("0x00407780")
|
|
&& line.contains("0x004077e0")
|
|
&& line.contains("[site+0x5bd]")
|
|
&& line.contains("0x1a-byte")
|
|
}));
|
|
assert!(trace.notes.iter().any(|line| {
|
|
line.contains("0x004093d0")
|
|
&& line.contains("0x00407bd0")
|
|
&& line.contains("+0x02")
|
|
&& line.contains("+0x06")
|
|
&& line.contains("+0x0a")
|
|
&& line.contains("+0x0e/+0x12/+0x16")
|
|
}));
|
|
assert!(trace.notes.iter().any(|line| {
|
|
line.contains("0x0062ba8c")
|
|
&& line.contains("0x0041f4e0")
|
|
&& line.contains("0x00494fb0")
|
|
&& line.contains("remaining linked-transit gap is narrower again")
|
|
}));
|
|
assert!(trace.notes.iter().any(|line| {
|
|
line.contains("0x00409720")
|
|
&& line.contains("0x004093d0")
|
|
&& line.contains("0x00407bd0")
|
|
&& line.contains("0x00409950")
|
|
}));
|
|
assert!(trace.notes.iter().any(|line| {
|
|
line.contains("0x0040eba0")
|
|
&& line.contains("[site+0x2a4]")
|
|
&& line.contains("0x004814c0 / 0x00481480")
|
|
&& line.contains("0x0042c9f0 / 0x0042c9a0")
|
|
}));
|
|
assert!(trace.notes.iter().any(|line| {
|
|
line.contains("0x0040ea96..0x0040eb65")
|
|
&& line.contains("[site+0x276]")
|
|
&& line.contains("consumes")
|
|
&& line.contains("rather than rehydrating")
|
|
}));
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x0041f7e0 / 0x0041f810 / 0x0041f850")
|
|
&& line.contains("[site+0x2a4]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x004269b0")
|
|
&& line.contains("0x0062b26c")
|
|
&& line.contains("[site+0x2a4]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(
|
|
|line| line.contains("0x004131f0 -> 0x00412fb0 -> 0x004120b0 -> 0x00412ab0")
|
|
&& line.contains("[candidate+0x32]")
|
|
)
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x0040dc40")
|
|
&& line.contains("[site+0x276]")
|
|
&& line.contains("0x0040d1f0 / 0x00480710"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line
|
|
.contains("0x00402cb0 -> 0x00403ed5/0x0040446b -> 0x004134d0 -> 0x0040ef10"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x00431b20")
|
|
&& line.contains("0x0061039c")
|
|
&& line.contains("0x00430040 / 0x00426d60 / 0x0042fc90"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x0040d450")
|
|
&& line.contains("[site+0x310]")
|
|
&& line.contains("0x00410b30..0x004118f4")
|
|
&& line.contains("0x00412560"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x0042bbf0")
|
|
&& line.contains("0x0042bbb0")
|
|
&& line.contains("0x0042c9f0")
|
|
&& line.contains("0x0042c9a0"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_runtime_backed_input_families
|
|
.iter()
|
|
.any(|line| line.contains("0x2329/0x0d"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_live_owner_families
|
|
.iter()
|
|
.any(|line| line.contains("0x0040d450") && line.contains("[site+0x310]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_live_owner_families
|
|
.iter()
|
|
.any(|line| line.contains("0x00410b30..0x004118f4")
|
|
&& line.contains("[site+0x310/+0x338/+0x360]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_live_owner_families
|
|
.iter()
|
|
.any(|line| line.contains("0x00412560"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_candidate_gate_fields
|
|
.iter()
|
|
.any(|line| line.contains("[+0x20/+0x22/+0x24/+0x28/+0x2c/+0x44]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_candidate_gate_fields
|
|
.iter()
|
|
.any(|line| line.contains("0x006cec78"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_candidate_gate_fields
|
|
.iter()
|
|
.any(|line| line.contains("0x0062ba8c"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_candidate_gate_fields
|
|
.iter()
|
|
.any(|line| line.contains("vtable slot +0x80") && line.contains("[site+0x246]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_candidate_gate_fields
|
|
.iter()
|
|
.any(|line| line.contains("0x0040fb8d")
|
|
&& line.contains("0x00410721")
|
|
&& line.contains("0x004126d3"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_runtime_writer_roles
|
|
.iter()
|
|
.any(|line| line.contains("0x0040d450") && line.contains("[site+0x310]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_runtime_writer_roles
|
|
.iter()
|
|
.any(|line| line.contains("0x00410b30..0x004118f4")
|
|
&& line.contains("[site+0x310/+0x338/+0x360]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_runtime_writer_roles
|
|
.iter()
|
|
.any(|line| line.contains("0x0041114a7/0x004111572")
|
|
&& line.contains("0x0041114b7/0x004111582"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_runtime_writer_roles
|
|
.iter()
|
|
.any(|line| line.contains("0x0041118aa/0x0041118f4"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_runtime_writer_roles
|
|
.iter()
|
|
.any(|line| line.contains("0x0040c9a0")
|
|
&& line.contains("[site+0x2b4/+0x2b8/+0x2bc]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_direct_caller_families
|
|
.iter()
|
|
.any(|line| line.contains("0x0040fb70")
|
|
&& line.contains("vtable slot +0x80")
|
|
&& line.contains("owner-present flag")
|
|
&& line.contains("0x00412560"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_direct_caller_families
|
|
.iter()
|
|
.any(|line| line.contains("0x004b4052 / 0x004b46ec")
|
|
&& line.contains("0x0062b26c"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_direct_caller_families
|
|
.iter()
|
|
.any(|line| line.contains("0x00401633") && line.contains("0x2329/0x0d"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_direct_caller_families
|
|
.iter()
|
|
.any(|line| line.contains("0x0044b81a")
|
|
&& line.contains("0x00436590")
|
|
&& line.contains("0x65"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_direct_caller_families
|
|
.iter()
|
|
.any(|line| line.contains("0x004b70f5 / 0x004b7979")
|
|
&& line.contains("0x004337a0")
|
|
&& line.contains("0x00540120 / 0x00518140"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_formula_input_lanes
|
|
.iter()
|
|
.any(|line| line.contains("[+0x20/+0x22]")
|
|
&& line.contains("[+0x24/+0x28]")
|
|
&& line.contains("[+0x44]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_formula_input_lanes
|
|
.iter()
|
|
.any(|line| line.contains("[world+0x0d]") && line.contains("[world+0x4afb]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_formula_input_lanes
|
|
.iter()
|
|
.any(|line| line.contains("0x00455810 / 0x00455800 / 0x0044ad60")
|
|
&& line.contains("[site+0x276]")
|
|
&& line.contains("0x66/0x68"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_formula_input_lanes
|
|
.iter()
|
|
.any(|line| line.contains("[+0x18/+0x1c/+0x2a/+0x2c/+0x44]")
|
|
&& line.contains("[site+0x78c]")
|
|
&& line.contains("[site+0x391]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_tri_lane_formula_input_lanes
|
|
.iter()
|
|
.any(|line| line.contains("[world+0x4caa]")
|
|
&& line.contains("[company+0x0d5d]")
|
|
&& line.contains("[site+0x310]")
|
|
&& line.contains("[site+0x360]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_remaining_owner_gaps
|
|
.iter()
|
|
.any(|line| line.contains("[site+0x276]")
|
|
&& line.contains("0x36b1/0x36b2/0x36b3")
|
|
&& line.contains("0x0047efe0")
|
|
&& line.contains("0x004134d0 / 0x0040f6d0")
|
|
&& line.contains("0x0040ef10")
|
|
&& line.contains("[+0x0c]")
|
|
&& line.contains("non-transport"))
|
|
);
|
|
assert!(
|
|
!trace
|
|
.near_city_acquisition_remaining_owner_gaps
|
|
.iter()
|
|
.any(|line| line.contains("[site+0x2a4]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_remaining_owner_gaps
|
|
.iter()
|
|
.any(|line| line.contains("0x0040d450")
|
|
&& line.contains("0x00410b30..0x004118f4")
|
|
&& line.contains("0x00412560"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_region_lane_statuses
|
|
.iter()
|
|
.any(|line| line.contains("[site+0x276]")
|
|
&& line.contains("0x004014b0")
|
|
&& line.contains("0x36b1/0x36b2/0x36b3")
|
|
&& line.contains("0x0047efe0")
|
|
&& line.contains("0x004134d0 / 0x0040f6d0")
|
|
&& line.contains("0x0040ef10"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_region_lane_statuses
|
|
.iter()
|
|
.any(|line| line.contains("[site+0x2a4]")
|
|
&& line.contains("record's own site id")
|
|
&& line.contains("0x00480210")
|
|
&& line.contains("0x004269b0")
|
|
&& line.contains("reconstructible from collection identity"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_region_lane_statuses
|
|
.iter()
|
|
.any(|line| line.contains("[site+0x310/+0x338/+0x360]")
|
|
&& line.contains("0x0040cac0")
|
|
&& line.contains("0x0040c9a0")
|
|
&& line.contains("0x0040d450")
|
|
&& line.contains("0x00410b30..0x004118f4")
|
|
&& line.contains("0x00412560"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.near_city_acquisition_region_lane_statuses
|
|
.iter()
|
|
.any(
|
|
|line| line.contains("0x004131f0 -> 0x00412fb0 -> 0x004120b0 -> 0x00412ab0")
|
|
&& line.contains("0x0040ce60")
|
|
&& line.contains("[site+0x3cc/+0x3d0]")
|
|
)
|
|
);
|
|
assert!(
|
|
trace
|
|
.peer_site_runtime_reconstruction_steps
|
|
.iter()
|
|
.any(|line| {
|
|
line.contains("[cell+0xd4]")
|
|
&& line.contains("[cell+0xd6]")
|
|
&& line.contains("0x0042bbf0/0x0042bbb0")
|
|
})
|
|
);
|
|
assert!(trace.next_owner_questions.iter().any(|line| {
|
|
line.contains("0x004160aa")
|
|
&& line.contains("0x0040ee10")
|
|
&& line.contains("0x0040edf6")
|
|
}));
|
|
let acquisition_branch = trace.companies[0]
|
|
.branches
|
|
.iter()
|
|
.find(|branch| branch.branch_name == "industry_acquisition_side_branch")
|
|
.expect("missing acquisition branch");
|
|
assert_eq!(
|
|
acquisition_branch.status,
|
|
"blocked_missing_near-city_owner_mapping"
|
|
);
|
|
assert!(
|
|
acquisition_branch
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x004014b0"))
|
|
);
|
|
assert!(
|
|
acquisition_branch
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x004019e0"))
|
|
);
|
|
assert!(
|
|
acquisition_branch
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x004010f0"))
|
|
);
|
|
assert!(
|
|
acquisition_branch
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x00420030 / 0x00420280"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00405920"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x0041f6e0") && line.contains("0x0042b2d0"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00480710") && line.contains("route-entry-anchor"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x0042bbf0")
|
|
&& line.contains("0x0042bbb0")
|
|
&& line.contains("[cell+0xd4]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x0042c9f0")
|
|
&& line.contains("0x0042c9a0")
|
|
&& line.contains("[cell+0xd6]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x004269b0")
|
|
&& line.contains("0x0062b26c")
|
|
&& line.contains("[site+0x276]/[site+0x27a]"))
|
|
);
|
|
assert!(trace.known_bridge_helpers.iter().any(
|
|
|line| line.contains("0x00426dce..0x00426ea1")
|
|
&& line.contains("0x0062b26c")
|
|
&& line.contains("non-subtype-4")
|
|
));
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00430040..0x004300d6")
|
|
&& line.contains("0x09/0x0b/0x0c"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00431b20")
|
|
&& line.contains("0x0061039c")
|
|
&& line.contains("0x00430040")
|
|
&& line.contains("0x00426d60")
|
|
&& line.contains("0x0042fc90"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x0042fc90")
|
|
&& line.contains("0x0062b26c")
|
|
&& line.contains("[site+0x276]")
|
|
&& line.contains("vtable slot +0x70"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x004134d0") && line.contains("0x0040f6d0"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00403ef3 / 0x00404489"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x0046f073 / 0x004707ff"))
|
|
);
|
|
assert!(trace.known_bridge_helpers.iter().any(|line| {
|
|
line.contains("0x0046f073 / 0x004707ff")
|
|
&& line.contains("[+0x0c]")
|
|
&& line.contains("0x0040f5d4")
|
|
}));
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x004706b0")
|
|
&& line.contains("selector-0x13")
|
|
&& line.contains("0x0040ef10"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00472b40")
|
|
&& line.contains("selector-0x72")
|
|
&& line.contains("0x00472bef / 0x00472d03"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00422bb4")
|
|
&& line.contains("0x0062b2fc")
|
|
&& line.contains("literal flags 1/0")
|
|
&& line.contains("out-param"))
|
|
);
|
|
assert!(trace.known_bridge_helpers.iter().any(|line| {
|
|
line.contains("0x00508fd1 / 0x005098eb")
|
|
&& line.contains("[this+0x7c]")
|
|
&& line.contains("0x0040eba0")
|
|
&& line.contains("arg3 forced to zero")
|
|
}));
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00473c20")
|
|
&& line.contains("0x006ce808..0x006ce988")
|
|
&& line.contains("0x00473c98"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x0042128d")
|
|
&& line.contains("0x00421430")
|
|
&& line.contains("[site+0x276]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00422305")
|
|
&& line.contains("event 0x7")
|
|
&& line.contains("[site+0x276]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x004269c9 / 0x00426a2a")
|
|
&& line.contains("[site+0x276]/[site+0x27a]"))
|
|
);
|
|
assert!(trace.known_bridge_helpers.iter().any(|line| {
|
|
line.contains("0x004282a9 / 0x004300d6")
|
|
&& line.contains("owner-transfer")
|
|
&& line.contains("placed-structure")
|
|
}));
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00413440")
|
|
&& line.contains("0x36b1/0x36b2/0x36b3")
|
|
&& line.contains("slot +0x44"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x0040f6d0")
|
|
&& line.contains("0x00481390")
|
|
&& line.contains("0x00480210"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x0040df27 / 0x0040e00a / 0x0040edf6"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00444690") && line.contains("0x004133b0"))
|
|
);
|
|
assert!(trace.known_bridge_helpers.iter().any(
|
|
|line| line.contains("0x0040e360..0x0040edf6")
|
|
&& line.contains("[site+0x2a8/+0x2a4/+0x276]")
|
|
&& line.contains("0x00426b10")
|
|
&& line.contains("0x00455860")
|
|
));
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x0040e450") && line.contains("queued site-id"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x004160aa") && line.contains("0x0040ee10"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x0040dc40")
|
|
&& line.contains("[site+0x276]")
|
|
&& line.contains("0x2329/0x0d"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00417840 / 0x004197e0 / 0x004142c0 / 0x004142d0"))
|
|
);
|
|
assert!(trace.known_bridge_helpers.iter().any(|line| line.contains(
|
|
"0x0040d1f0 / 0x00480710 / 0x0045b160 / 0x0045b9b0 / 0x00418be0 / 0x0040cd70"
|
|
)));
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00402cb0 / 0x00403ed5 / 0x0040446b"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x004134d0 / 0x0040ef10"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00481430 / 0x0047d8e0"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x0040c9a0")
|
|
&& line.contains("[site+0x310/+0x338/+0x360]")
|
|
&& line.contains("[site+0x2b4/+0x2b8/+0x2bc]"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x0040d450")
|
|
&& line.contains("[site+0x310]")
|
|
&& line.contains("0x00436590"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x0040fb70")
|
|
&& line.contains("vtable slot +0x80")
|
|
&& line.contains("0x00412560"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00412560")
|
|
&& line.contains("0x0062ba8c")
|
|
&& line.contains("world date/flags"))
|
|
);
|
|
assert!(trace.known_bridge_helpers.iter().any(
|
|
|line| line.contains("0x00410b30..0x004118f4")
|
|
&& line.contains("[site+0x310/+0x338/+0x360]")
|
|
&& line.contains("0x00412560")
|
|
));
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00401633") && line.contains("0x2329/0x0d"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x0044b81a")
|
|
&& line.contains("0x00436590")
|
|
&& line.contains("0x65"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x004b4052 / 0x004b46ec")
|
|
&& line.contains("0x0062b26c"))
|
|
);
|
|
assert!(trace.known_bridge_helpers.iter().any(|line| {
|
|
line.contains("0x004b70f5 / 0x004b7979")
|
|
&& line.contains("0x004337a0")
|
|
&& line.contains("0x00540120 / 0x00518140")
|
|
}));
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x0040a3a1..0x0040a4d3") && line.contains("0x0040c9a0"))
|
|
);
|
|
assert!(trace.known_bridge_helpers.iter().any(
|
|
|line| line.contains("0x0040fcc0..0x0040fe28")
|
|
&& line.contains("0x00422c62..0x00422d3c")
|
|
&& line.contains("0x0040cac0")
|
|
));
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x005c8c50 +0x40")
|
|
&& line.contains("0x0040ce60")
|
|
&& line.contains("0x0040cd70 / 0x0045c150"))
|
|
);
|
|
let city_branch = trace.companies[0]
|
|
.branches
|
|
.iter()
|
|
.find(|branch| branch.branch_name == "city_connection_announcement")
|
|
.expect("missing city branch");
|
|
assert!(
|
|
city_branch
|
|
.candidate_consumers
|
|
.iter()
|
|
.any(|line| line.contains("0x00406050"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn builds_infrastructure_asset_trace_report_with_alias_disproved_status() {
|
|
let mut analysis = empty_analysis_report();
|
|
analysis.placed_structure_record_triplets =
|
|
Some(SmpSavePlacedStructureRecordTripletProbe {
|
|
profile_family: analysis.profile_family.clone(),
|
|
source_kind: "save-placed-structure-triplets".to_string(),
|
|
semantic_family: "placed-structure-triplets".to_string(),
|
|
records_tag_offset: 0,
|
|
close_tag_offset: 0,
|
|
record_count: 2057,
|
|
entries: Vec::new(),
|
|
evidence: Vec::new(),
|
|
});
|
|
analysis.placed_structure_dynamic_side_buffer =
|
|
Some(SmpSavePlacedStructureDynamicSideBufferProbe {
|
|
profile_family: analysis.profile_family.clone(),
|
|
source_kind: "save-side-buffer".to_string(),
|
|
semantic_family: "infrastructure-asset".to_string(),
|
|
metadata_tag_offset: 0,
|
|
records_tag_offset: 0,
|
|
close_tag_offset: 0,
|
|
records_span_len: 0,
|
|
direct_record_stride: 6,
|
|
direct_record_stride_hex: "0x00000006".to_string(),
|
|
live_id_bound: 3865,
|
|
live_id_bound_hex: "0x00000f19".to_string(),
|
|
live_record_count: 3865,
|
|
live_record_count_hex: "0x00000f19".to_string(),
|
|
owner_shared_dword: 0xff000000,
|
|
owner_shared_dword_hex: "0xff000000".to_string(),
|
|
owner_shared_dword_relative_offset: 0,
|
|
owner_shared_dword_matches_first_compact_prefix_leading_dword: true,
|
|
first_record_child_count_after_owner_shared: Some(1),
|
|
first_record_child_count_after_owner_shared_hex: Some("0x0001".to_string()),
|
|
first_record_saved_primary_child_byte_after_owner_shared: Some(0xff),
|
|
first_record_saved_primary_child_byte_after_owner_shared_hex: Some(
|
|
"0xff".to_string(),
|
|
),
|
|
first_record_first_name_tag_relative_offset_after_owner_shared: Some(3),
|
|
prefix_leading_dword: 0xff000000,
|
|
prefix_leading_dword_hex: "0xff000000".to_string(),
|
|
prefix_trailing_word: 1,
|
|
prefix_trailing_word_hex: "0x0001".to_string(),
|
|
prefix_separator_byte: 0xff,
|
|
prefix_separator_byte_hex: "0xff".to_string(),
|
|
first_embedded_name_tag_relative_offset: 0x20,
|
|
embedded_name_tag_count: 138,
|
|
decoded_embedded_name_row_count: 138,
|
|
decoded_embedded_name_row_with_tertiary_name_count: 0,
|
|
unique_compact_prefix_pattern_count: 7,
|
|
prefix_leading_dword_matching_embedded_profile_tag_count: 17,
|
|
unique_embedded_name_pair_count: 5,
|
|
first_embedded_primary_name: Some("TrackCapST_Cap.3dp".to_string()),
|
|
first_embedded_secondary_name: Some("Infrastructure".to_string()),
|
|
first_embedded_tertiary_name: None,
|
|
embedded_name_row_samples: Vec::new(),
|
|
compact_prefix_pattern_summaries: Vec::new(),
|
|
name_pair_summaries: vec![SmpSavePlacedStructureDynamicSideBufferNamePairSummary {
|
|
primary_name: "TrackCapST_Cap.3dp".to_string(),
|
|
secondary_name: "Infrastructure".to_string(),
|
|
count: 12,
|
|
first_name_tag_relative_offset: 0x20,
|
|
unique_compact_prefix_pattern_count: 2,
|
|
dominant_prefix_leading_dword: 0xff0000ff,
|
|
dominant_prefix_leading_dword_hex: "0xff0000ff".to_string(),
|
|
dominant_prefix_trailing_word: 1,
|
|
dominant_prefix_trailing_word_hex: "0x0001".to_string(),
|
|
dominant_prefix_separator_byte: 0xff,
|
|
dominant_prefix_separator_byte_hex: "0xff".to_string(),
|
|
dominant_prefix_count: 9,
|
|
}],
|
|
payload_envelope_summary: Some(
|
|
SmpSavePlacedStructureDynamicSideBufferPayloadEnvelopeSummary {
|
|
row_count_with_policy_tag_before_next_name: 120,
|
|
row_count_with_complete_0x55f1_0x55f2_0x55f3_envelope: 118,
|
|
row_count_missing_policy_tag_before_next_name: 18,
|
|
row_count_missing_profile_tag_after_policy: 2,
|
|
unique_policy_chunk_lens: vec![0x1a, 0x24],
|
|
unique_profile_chunk_lens: vec![0x08, 0x14],
|
|
dominant_policy_chunk_len: Some(0x1a),
|
|
dominant_policy_chunk_len_count: 110,
|
|
dominant_profile_chunk_len: Some(0x08),
|
|
dominant_profile_chunk_len_count: 90,
|
|
short_profile_flag_pair_summary: Some(
|
|
SmpSavePlacedStructureDynamicSideBufferShortProfileFlagPairSummary {
|
|
row_count_with_0x06_profile_span: 72,
|
|
unique_flag_pair_count: 2,
|
|
dominant_first_flag_byte: Some(0x01),
|
|
dominant_first_flag_byte_hex: Some("0x01".to_string()),
|
|
dominant_first_flag_byte_count: 60,
|
|
dominant_second_flag_byte: Some(0x00),
|
|
dominant_second_flag_byte_hex: Some("0x00".to_string()),
|
|
dominant_second_flag_byte_count: 55,
|
|
dominant_flag_pair: Some(
|
|
SmpSavePlacedStructureDynamicSideBufferShortProfileFlagPair {
|
|
first_flag_byte: 0x01,
|
|
first_flag_byte_hex: "0x01".to_string(),
|
|
second_flag_byte: 0x00,
|
|
second_flag_byte_hex: "0x00".to_string(),
|
|
count: 48,
|
|
},
|
|
),
|
|
sample_rows: Vec::new(),
|
|
},
|
|
),
|
|
fixed_policy_summary: Some(
|
|
SmpSavePlacedStructureDynamicSideBufferFixedPolicySummary {
|
|
row_count_with_0x1a_policy_chunk: 118,
|
|
unique_trailing_word_count: 1,
|
|
dominant_trailing_word: Some(1),
|
|
dominant_trailing_word_hex: Some("0x0001".to_string()),
|
|
dominant_trailing_word_count: 118,
|
|
compact_prefix_correlations: Vec::new(),
|
|
sample_rows: Vec::new(),
|
|
},
|
|
),
|
|
name_prelude_candidate_summary: Some(
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidateSummary {
|
|
row_count_with_candidate_window: 118,
|
|
unique_candidate_pattern_count: 2,
|
|
dominant_child_count_candidate: Some(1),
|
|
dominant_child_count_candidate_count: 110,
|
|
dominant_saved_primary_child_byte_candidate: Some(0xff),
|
|
dominant_saved_primary_child_byte_candidate_hex: Some(
|
|
"0xff".to_string(),
|
|
),
|
|
dominant_saved_primary_child_byte_candidate_count: 110,
|
|
dominant_candidate_pattern: Some(
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern {
|
|
child_count_candidate: 1,
|
|
child_count_candidate_hex: "0x0001".to_string(),
|
|
saved_primary_child_byte_candidate: 0xff,
|
|
saved_primary_child_byte_candidate_hex: "0xff".to_string(),
|
|
count: 110,
|
|
},
|
|
),
|
|
candidate_pattern_correlations: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePatternCorrelation {
|
|
child_count_candidate: 2,
|
|
child_count_candidate_hex: "0x0002".to_string(),
|
|
saved_primary_child_byte_candidate: 0xff,
|
|
saved_primary_child_byte_candidate_hex: "0xff".to_string(),
|
|
row_count: 18,
|
|
unique_name_pair_count: 1,
|
|
unique_profile_span_count: 1,
|
|
dominant_primary_name: Some(
|
|
"BridgeSTWood_Section.3dp".to_string(),
|
|
),
|
|
dominant_secondary_name: Some(
|
|
"Infrastructure".to_string(),
|
|
),
|
|
dominant_name_pair_count: 18,
|
|
dominant_profile_span: Some(6),
|
|
dominant_profile_span_count: 10,
|
|
dominant_mode_family: Some("bridge".to_string()),
|
|
dominant_mode_family_count: 18,
|
|
mode_family_counts: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount {
|
|
mode_family: "bridge".to_string(),
|
|
count: 18,
|
|
},
|
|
],
|
|
sample_rows: Vec::new(),
|
|
},
|
|
],
|
|
profile_span_correlations: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanCorrelation {
|
|
previous_profile_chunk_len_to_next_name_or_end: 3,
|
|
row_count: 17,
|
|
dominant_child_count_candidate: Some(1),
|
|
dominant_child_count_candidate_count: 17,
|
|
dominant_saved_primary_child_byte_candidate: Some(0xff),
|
|
dominant_saved_primary_child_byte_candidate_hex: Some(
|
|
"0xff".to_string(),
|
|
),
|
|
dominant_saved_primary_child_byte_candidate_count: 17,
|
|
dominant_candidate_pattern: Some(
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern {
|
|
child_count_candidate: 1,
|
|
child_count_candidate_hex: "0x0001".to_string(),
|
|
saved_primary_child_byte_candidate: 0xff,
|
|
saved_primary_child_byte_candidate_hex: "0xff"
|
|
.to_string(),
|
|
count: 17,
|
|
},
|
|
),
|
|
dominant_mode_family: Some("tunnel".to_string()),
|
|
dominant_mode_family_count: 15,
|
|
mode_family_counts: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount {
|
|
mode_family: "track_cap".to_string(),
|
|
count: 2,
|
|
},
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount {
|
|
mode_family: "tunnel".to_string(),
|
|
count: 15,
|
|
},
|
|
],
|
|
compact_prefix_pattern_summaries: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanPrefixSummary {
|
|
prefix_leading_dword: 0x0000_55f3,
|
|
prefix_leading_dword_hex: "0x000055f3".to_string(),
|
|
prefix_trailing_word: 0x0001,
|
|
prefix_trailing_word_hex: "0x0001".to_string(),
|
|
prefix_separator_byte: 0xff,
|
|
prefix_separator_byte_hex: "0xff".to_string(),
|
|
count: 17,
|
|
},
|
|
],
|
|
sample_rows: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanSample {
|
|
sample_index: 0,
|
|
name_tag_relative_offset: 1200,
|
|
primary_name: Some("TunnelSTBrick_Section.3dp".to_string()),
|
|
secondary_name: Some("Infrastructure".to_string()),
|
|
prefix_leading_dword: 0x0000_55f3,
|
|
prefix_leading_dword_hex: "0x000055f3".to_string(),
|
|
prefix_trailing_word: 0x0001,
|
|
prefix_trailing_word_hex: "0x0001".to_string(),
|
|
prefix_separator_byte: 0xff,
|
|
prefix_separator_byte_hex: "0xff".to_string(),
|
|
child_count_candidate: 1,
|
|
child_count_candidate_hex: "0x0001".to_string(),
|
|
saved_primary_child_byte_candidate: 0xff,
|
|
saved_primary_child_byte_candidate_hex: "0xff".to_string(),
|
|
},
|
|
],
|
|
},
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanCorrelation {
|
|
previous_profile_chunk_len_to_next_name_or_end: 6,
|
|
row_count: 72,
|
|
dominant_child_count_candidate: Some(1),
|
|
dominant_child_count_candidate_count: 62,
|
|
dominant_saved_primary_child_byte_candidate: Some(0xff),
|
|
dominant_saved_primary_child_byte_candidate_hex: Some(
|
|
"0xff".to_string(),
|
|
),
|
|
dominant_saved_primary_child_byte_candidate_count: 72,
|
|
dominant_candidate_pattern: Some(
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern {
|
|
child_count_candidate: 1,
|
|
child_count_candidate_hex: "0x0001".to_string(),
|
|
saved_primary_child_byte_candidate: 0xff,
|
|
saved_primary_child_byte_candidate_hex: "0xff"
|
|
.to_string(),
|
|
count: 62,
|
|
},
|
|
),
|
|
dominant_mode_family: Some("bridge".to_string()),
|
|
dominant_mode_family_count: 72,
|
|
mode_family_counts: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount {
|
|
mode_family: "bridge".to_string(),
|
|
count: 72,
|
|
},
|
|
],
|
|
compact_prefix_pattern_summaries: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanPrefixSummary {
|
|
prefix_leading_dword: 0xff00_0000,
|
|
prefix_leading_dword_hex: "0xff000000".to_string(),
|
|
prefix_trailing_word: 0x0001,
|
|
prefix_trailing_word_hex: "0x0001".to_string(),
|
|
prefix_separator_byte: 0xff,
|
|
prefix_separator_byte_hex: "0xff".to_string(),
|
|
count: 72,
|
|
},
|
|
],
|
|
sample_rows: vec![],
|
|
},
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanCorrelation {
|
|
previous_profile_chunk_len_to_next_name_or_end: 0x27,
|
|
row_count: 3,
|
|
dominant_child_count_candidate: Some(1),
|
|
dominant_child_count_candidate_count: 3,
|
|
dominant_saved_primary_child_byte_candidate: Some(0xff),
|
|
dominant_saved_primary_child_byte_candidate_hex: Some(
|
|
"0xff".to_string(),
|
|
),
|
|
dominant_saved_primary_child_byte_candidate_count: 3,
|
|
dominant_candidate_pattern: Some(
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern {
|
|
child_count_candidate: 1,
|
|
child_count_candidate_hex: "0x0001".to_string(),
|
|
saved_primary_child_byte_candidate: 0xff,
|
|
saved_primary_child_byte_candidate_hex: "0xff"
|
|
.to_string(),
|
|
count: 3,
|
|
},
|
|
),
|
|
dominant_mode_family: Some("bridge".to_string()),
|
|
dominant_mode_family_count: 2,
|
|
mode_family_counts: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount {
|
|
mode_family: "bridge".to_string(),
|
|
count: 2,
|
|
},
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount {
|
|
mode_family: "tunnel".to_string(),
|
|
count: 1,
|
|
},
|
|
],
|
|
compact_prefix_pattern_summaries: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanPrefixSummary {
|
|
prefix_leading_dword: 0xff00_00ff,
|
|
prefix_leading_dword_hex: "0xff0000ff".to_string(),
|
|
prefix_trailing_word: 0x0001,
|
|
prefix_trailing_word_hex: "0x0001".to_string(),
|
|
prefix_separator_byte: 0xff,
|
|
prefix_separator_byte_hex: "0xff".to_string(),
|
|
count: 1,
|
|
},
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanPrefixSummary {
|
|
prefix_leading_dword: 0xff00_00ff,
|
|
prefix_leading_dword_hex: "0xff0000ff".to_string(),
|
|
prefix_trailing_word: 0x0002,
|
|
prefix_trailing_word_hex: "0x0002".to_string(),
|
|
prefix_separator_byte: 0xff,
|
|
prefix_separator_byte_hex: "0xff".to_string(),
|
|
count: 2,
|
|
},
|
|
],
|
|
sample_rows: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanSample {
|
|
sample_index: 0,
|
|
name_tag_relative_offset: 2805,
|
|
primary_name: Some("TunnelSTBrick_Section.3dp".to_string()),
|
|
secondary_name: Some("Infrastructure".to_string()),
|
|
prefix_leading_dword: 0xff00_00ff,
|
|
prefix_leading_dword_hex: "0xff0000ff".to_string(),
|
|
prefix_trailing_word: 0x0001,
|
|
prefix_trailing_word_hex: "0x0001".to_string(),
|
|
prefix_separator_byte: 0xff,
|
|
prefix_separator_byte_hex: "0xff".to_string(),
|
|
child_count_candidate: 1,
|
|
child_count_candidate_hex: "0x0001".to_string(),
|
|
saved_primary_child_byte_candidate: 0xff,
|
|
saved_primary_child_byte_candidate_hex: "0xff".to_string(),
|
|
},
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeProfileSpanSample {
|
|
sample_index: 1,
|
|
name_tag_relative_offset: 3764,
|
|
primary_name: Some("BridgeSTWood_Section.3dp".to_string()),
|
|
secondary_name: Some("Infrastructure".to_string()),
|
|
prefix_leading_dword: 0xff00_00ff,
|
|
prefix_leading_dword_hex: "0xff0000ff".to_string(),
|
|
prefix_trailing_word: 0x0002,
|
|
prefix_trailing_word_hex: "0x0002".to_string(),
|
|
prefix_separator_byte: 0xff,
|
|
prefix_separator_byte_hex: "0xff".to_string(),
|
|
child_count_candidate: 1,
|
|
child_count_candidate_hex: "0x0001".to_string(),
|
|
saved_primary_child_byte_candidate: 0xff,
|
|
saved_primary_child_byte_candidate_hex: "0xff".to_string(),
|
|
},
|
|
],
|
|
},
|
|
],
|
|
compact_prefix_correlations: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixCorrelation {
|
|
prefix_leading_dword: 0x0000_55f3,
|
|
prefix_leading_dword_hex: "0x000055f3".to_string(),
|
|
prefix_trailing_word: 0x0001,
|
|
prefix_trailing_word_hex: "0x0001".to_string(),
|
|
prefix_separator_byte: 0xff,
|
|
prefix_separator_byte_hex: "0xff".to_string(),
|
|
row_count: 17,
|
|
unique_name_pair_count: 2,
|
|
unique_profile_span_count: 1,
|
|
dominant_primary_name: Some("TunnelSTBrick_Section.3dp".to_string()),
|
|
dominant_secondary_name: Some("Infrastructure".to_string()),
|
|
dominant_name_pair_count: 15,
|
|
dominant_profile_span: Some(3),
|
|
dominant_profile_span_count: 17,
|
|
dominant_candidate_pattern: Some(
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern {
|
|
child_count_candidate: 1,
|
|
child_count_candidate_hex: "0x0001".to_string(),
|
|
saved_primary_child_byte_candidate: 0xff,
|
|
saved_primary_child_byte_candidate_hex: "0xff".to_string(),
|
|
count: 17,
|
|
},
|
|
),
|
|
dominant_mode_family: Some("tunnel".to_string()),
|
|
dominant_mode_family_count: 15,
|
|
mode_family_counts: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount {
|
|
mode_family: "track_cap".to_string(),
|
|
count: 2,
|
|
},
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount {
|
|
mode_family: "tunnel".to_string(),
|
|
count: 15,
|
|
},
|
|
],
|
|
name_pair_summaries: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanNamePairSummary {
|
|
primary_name: Some("TunnelSTBrick_Section.3dp".to_string()),
|
|
secondary_name: Some("Infrastructure".to_string()),
|
|
count: 15,
|
|
},
|
|
SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanNamePairSummary {
|
|
primary_name: Some("TrackCapST_Cap.3dp".to_string()),
|
|
secondary_name: Some("Infrastructure".to_string()),
|
|
count: 2,
|
|
},
|
|
],
|
|
profile_span_counts: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixProfileSpanCount {
|
|
previous_profile_chunk_len_to_next_name_or_end: 3,
|
|
count: 17,
|
|
},
|
|
],
|
|
rows_with_previous_short_profile_flag_pair: 17,
|
|
previous_short_profile_flag_pair_counts: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixFlagPairCount {
|
|
first_flag_byte: 0x00,
|
|
first_flag_byte_hex: "0x00".to_string(),
|
|
second_flag_byte: 0x01,
|
|
second_flag_byte_hex: "0x01".to_string(),
|
|
count: 17,
|
|
},
|
|
],
|
|
sample_rows: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixSample {
|
|
sample_index: 0,
|
|
name_tag_relative_offset: 1200,
|
|
primary_name: Some("TunnelSTBrick_Section.3dp".to_string()),
|
|
secondary_name: Some("Infrastructure".to_string()),
|
|
child_count_candidate: 1,
|
|
child_count_candidate_hex: "0x0001".to_string(),
|
|
saved_primary_child_byte_candidate: 0xff,
|
|
saved_primary_child_byte_candidate_hex: "0xff".to_string(),
|
|
previous_profile_chunk_len_to_next_name_or_end: Some(3),
|
|
},
|
|
],
|
|
},
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixCorrelation {
|
|
prefix_leading_dword: 0xff00_00ff,
|
|
prefix_leading_dword_hex: "0xff0000ff".to_string(),
|
|
prefix_trailing_word: 0x0001,
|
|
prefix_trailing_word_hex: "0x0001".to_string(),
|
|
prefix_separator_byte: 0xff,
|
|
prefix_separator_byte_hex: "0xff".to_string(),
|
|
row_count: 1,
|
|
unique_name_pair_count: 1,
|
|
unique_profile_span_count: 1,
|
|
dominant_primary_name: Some("TunnelSTBrick_Section.3dp".to_string()),
|
|
dominant_secondary_name: Some("Infrastructure".to_string()),
|
|
dominant_name_pair_count: 1,
|
|
dominant_profile_span: Some(0x27),
|
|
dominant_profile_span_count: 1,
|
|
dominant_candidate_pattern: Some(
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern {
|
|
child_count_candidate: 1,
|
|
child_count_candidate_hex: "0x0001".to_string(),
|
|
saved_primary_child_byte_candidate: 0xff,
|
|
saved_primary_child_byte_candidate_hex: "0xff".to_string(),
|
|
count: 1,
|
|
},
|
|
),
|
|
dominant_mode_family: Some("tunnel".to_string()),
|
|
dominant_mode_family_count: 1,
|
|
mode_family_counts: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount {
|
|
mode_family: "tunnel".to_string(),
|
|
count: 1,
|
|
},
|
|
],
|
|
name_pair_summaries: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanNamePairSummary {
|
|
primary_name: Some("TunnelSTBrick_Section.3dp".to_string()),
|
|
secondary_name: Some("Infrastructure".to_string()),
|
|
count: 1,
|
|
},
|
|
],
|
|
profile_span_counts: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixProfileSpanCount {
|
|
previous_profile_chunk_len_to_next_name_or_end: 0x27,
|
|
count: 1,
|
|
},
|
|
],
|
|
rows_with_previous_short_profile_flag_pair: 1,
|
|
previous_short_profile_flag_pair_counts: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixFlagPairCount {
|
|
first_flag_byte: 0x00,
|
|
first_flag_byte_hex: "0x00".to_string(),
|
|
second_flag_byte: 0x01,
|
|
second_flag_byte_hex: "0x01".to_string(),
|
|
count: 1,
|
|
},
|
|
],
|
|
sample_rows: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixSample {
|
|
sample_index: 0,
|
|
name_tag_relative_offset: 2805,
|
|
primary_name: Some("TunnelSTBrick_Section.3dp".to_string()),
|
|
secondary_name: Some("Infrastructure".to_string()),
|
|
child_count_candidate: 1,
|
|
child_count_candidate_hex: "0x0001".to_string(),
|
|
saved_primary_child_byte_candidate: 0xff,
|
|
saved_primary_child_byte_candidate_hex: "0xff".to_string(),
|
|
previous_profile_chunk_len_to_next_name_or_end: Some(0x27),
|
|
},
|
|
],
|
|
},
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixCorrelation {
|
|
prefix_leading_dword: 0xff00_00ff,
|
|
prefix_leading_dword_hex: "0xff0000ff".to_string(),
|
|
prefix_trailing_word: 0x0002,
|
|
prefix_trailing_word_hex: "0x0002".to_string(),
|
|
prefix_separator_byte: 0xff,
|
|
prefix_separator_byte_hex: "0xff".to_string(),
|
|
row_count: 2,
|
|
unique_name_pair_count: 1,
|
|
unique_profile_span_count: 1,
|
|
dominant_primary_name: Some("BridgeSTWood_Section.3dp".to_string()),
|
|
dominant_secondary_name: Some("Infrastructure".to_string()),
|
|
dominant_name_pair_count: 2,
|
|
dominant_profile_span: Some(0x27),
|
|
dominant_profile_span_count: 2,
|
|
dominant_candidate_pattern: Some(
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCandidatePattern {
|
|
child_count_candidate: 1,
|
|
child_count_candidate_hex: "0x0001".to_string(),
|
|
saved_primary_child_byte_candidate: 0xff,
|
|
saved_primary_child_byte_candidate_hex: "0xff".to_string(),
|
|
count: 2,
|
|
},
|
|
),
|
|
dominant_mode_family: Some("bridge".to_string()),
|
|
dominant_mode_family_count: 2,
|
|
mode_family_counts: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeModeFamilyCount {
|
|
mode_family: "bridge".to_string(),
|
|
count: 2,
|
|
},
|
|
],
|
|
name_pair_summaries: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferDominantProfileSpanNamePairSummary {
|
|
primary_name: Some("BridgeSTWood_Section.3dp".to_string()),
|
|
secondary_name: Some("Infrastructure".to_string()),
|
|
count: 2,
|
|
},
|
|
],
|
|
profile_span_counts: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixProfileSpanCount {
|
|
previous_profile_chunk_len_to_next_name_or_end: 0x27,
|
|
count: 2,
|
|
},
|
|
],
|
|
rows_with_previous_short_profile_flag_pair: 2,
|
|
previous_short_profile_flag_pair_counts: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixFlagPairCount {
|
|
first_flag_byte: 0x00,
|
|
first_flag_byte_hex: "0x00".to_string(),
|
|
second_flag_byte: 0x01,
|
|
second_flag_byte_hex: "0x01".to_string(),
|
|
count: 2,
|
|
},
|
|
],
|
|
sample_rows: vec![
|
|
SmpSavePlacedStructureDynamicSideBufferNamePreludeCompactPrefixSample {
|
|
sample_index: 0,
|
|
name_tag_relative_offset: 3764,
|
|
primary_name: Some("BridgeSTWood_Section.3dp".to_string()),
|
|
secondary_name: Some("Infrastructure".to_string()),
|
|
child_count_candidate: 1,
|
|
child_count_candidate_hex: "0x0001".to_string(),
|
|
saved_primary_child_byte_candidate: 0xff,
|
|
saved_primary_child_byte_candidate_hex: "0xff".to_string(),
|
|
previous_profile_chunk_len_to_next_name_or_end: Some(0x27),
|
|
},
|
|
],
|
|
},
|
|
],
|
|
sample_rows: Vec::new(),
|
|
},
|
|
),
|
|
dominant_profile_span_class_summary: None,
|
|
sample_rows: Vec::new(),
|
|
},
|
|
),
|
|
live_entry_prelude_summary: Some(
|
|
SmpSavePlacedStructureDynamicSideBufferLiveEntryPreludeSummary {
|
|
live_entry_directory_row_count: 3865,
|
|
decoded_live_entry_id_count: 3865,
|
|
payload_relative_offset_monotonic: true,
|
|
rows_with_payload_pointer_inside_records_span: 138,
|
|
rows_with_zero_child_count: 0,
|
|
rows_with_nonzero_child_count: 138,
|
|
rows_with_first_name_tag_after_prelude: 138,
|
|
rows_with_first_name_tag_at_offset_3: 138,
|
|
unique_child_count_values: vec![1],
|
|
unique_first_name_tag_relative_offsets: vec![3],
|
|
dominant_child_count: Some(1),
|
|
dominant_child_count_count: 138,
|
|
dominant_saved_primary_child_byte: Some(0),
|
|
dominant_saved_primary_child_byte_hex: Some("0x00".to_string()),
|
|
dominant_saved_primary_child_byte_count: 138,
|
|
dominant_first_name_tag_relative_offset: Some(3),
|
|
dominant_first_name_tag_relative_offset_count: 138,
|
|
sample_rows: Vec::new(),
|
|
},
|
|
),
|
|
evidence: Vec::new(),
|
|
});
|
|
analysis.placed_structure_dynamic_side_buffer_alignment =
|
|
Some(SmpSavePlacedStructureDynamicSideBufferAlignmentProbe {
|
|
unique_side_buffer_name_pair_count: 5,
|
|
unique_triplet_name_pair_count: 56,
|
|
overlapping_name_pair_count: 0,
|
|
side_buffer_row_count: 138,
|
|
side_buffer_rows_with_matching_triplet_name_pair_count: 0,
|
|
side_buffer_rows_without_matching_triplet_name_pair_count: 138,
|
|
triplet_name_pairs_without_side_buffer_match_count: 56,
|
|
matched_name_pair_samples: Vec::new(),
|
|
unmatched_side_buffer_name_pair_samples: Vec::new(),
|
|
evidence: Vec::new(),
|
|
});
|
|
|
|
let trace = build_infrastructure_asset_trace_report(&analysis);
|
|
assert!(trace.side_buffer_present);
|
|
assert_eq!(trace.triplet_alignment_overlap_count, 0);
|
|
assert_eq!(trace.known_owner_bridge_fields.len(), 7);
|
|
assert_eq!(trace.known_bridge_helpers.len(), 21);
|
|
assert_eq!(trace.next_owner_questions.len(), 4);
|
|
assert!(trace.next_owner_questions.iter().any(|line| {
|
|
line.contains("compact-prefix regimes subdivide")
|
|
&& line.contains("0x0a BallastCap")
|
|
&& line.contains("0x0b TrackCap")
|
|
&& line.contains("0x02 Tunnel")
|
|
&& line.contains("0x01 Bridge")
|
|
}));
|
|
assert!(trace.next_owner_questions.iter().any(|line| {
|
|
line.contains("direct route-entry bridge helpers")
|
|
&& line.contains("0x00448a70/0x00493660/0x0048b660")
|
|
&& line.contains("[this+0x248]")
|
|
&& line.contains("cache/cleanup state")
|
|
}));
|
|
assert!(trace.known_bridge_helpers.iter().any(
|
|
|line| line.contains("0x004a2c80/0x004a34e0")
|
|
&& line.contains("paired upstream infrastructure composition choosers")
|
|
&& line.contains("BallastCap/Overpass")
|
|
));
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x0048a340")
|
|
&& line.contains("[0x226]/[0x219]/[0x251]/bit 0x20"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00455a40") && line.contains("slot +0x44"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x004559d0") && line.contains("0x55f1/0x55f2/0x55f3"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00490960") && line.contains("selector propagator"))
|
|
);
|
|
assert!(
|
|
trace
|
|
.known_bridge_helpers
|
|
.iter()
|
|
.any(|line| line.contains("0x00490200") && line.contains("route/link comparator"))
|
|
);
|
|
assert_eq!(trace.candidate_consumer_hypotheses.len(), 3);
|
|
assert_eq!(
|
|
trace.candidate_consumer_hypotheses[0].status,
|
|
"highest_priority_static_mapping_target"
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x004a2c80 routes the DT family")
|
|
&& line.contains("0x004a34e0 routes the ST family")
|
|
&& line.contains("0x0048a340/0x0048f4c0/0x00490200/0x00490960"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x621a44/0x621a54 feed BridgeST")
|
|
&& line.contains("0x621a94 feeds TunnelDT variants")
|
|
&& line.contains("BallastCapDT/ST")
|
|
&& line.contains("OverpassDT/ST"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(
|
|
|line| line.contains("[this+0x226]==1 routes bridge families")
|
|
&& line.contains("[this+0x226]==2 routes tunnel families")
|
|
&& line.contains("[this+0x226]==3 routes overpass/ballast families")
|
|
&& line.contains("bit 0x20 in [this+0x24c]")
|
|
)
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(
|
|
|line| line.contains("0x0048a340 as the exact chooser-state setter")
|
|
&& line.contains("[this+0x226]")
|
|
&& line.contains("[this+0x219]")
|
|
&& line.contains("[this+0x251]")
|
|
&& line.contains("[this+0x24c]")
|
|
)
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(
|
|
|line| line.contains("[this+0x219] indexes Steel/Stone/Wood tables")
|
|
&& line.contains("value 2 takes the special suspension-cap path")
|
|
&& line.contains("[this+0x251] selects Brick versus Concrete")
|
|
)
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("[this+0x252]")
|
|
&& line.contains("R10, L10, 12, 14, 16, and 18")
|
|
&& line.contains("BridgeDT/BridgeST suspension-cap literals"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.blockers
|
|
.iter()
|
|
.any(
|
|
|line| line.contains("remaining mixed exact compact-prefix classes")
|
|
&& line.contains("0xff0000ff/0x0002/0xff is pure bridge")
|
|
&& line.contains("0x0005d368/0x0001/0xff is pure track-cap")
|
|
&& line.contains("0xff0000ff/0x0001/0xff")
|
|
&& line.contains("0x000055f3/0x0001/0xff")
|
|
)
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains(
|
|
"profile-span mode-family correlations now also split the previous 0x55f3 spans directly"
|
|
) && line.contains("span=0x6 rows=72")
|
|
&& line.contains("span=0x3 rows=17")
|
|
&& line.contains("span=0x27 rows=3"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains(
|
|
"exact compact-prefix correlations now split the residual prelude classes directly"
|
|
) && line.contains("0x000055f3/0x0001/0xff")
|
|
&& line.contains("0xff0000ff/0x0001/0xff")
|
|
&& line.contains("0xff0000ff/0x0002/0xff"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("short 0x03-byte post-profile gaps")
|
|
&& line.contains("track_cap:2")
|
|
&& line.contains("tunnel:15")
|
|
&& line.contains("0x000055f3/0x0001/0xff:17"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("sparse 0x27 post-profile outlier")
|
|
&& line.contains("0xff0000ff/0x0001/0xff:1")
|
|
&& line.contains("0xff0000ff/0x0002/0xff:2")
|
|
&& line.contains("TunnelSTBrick_Section.3dp")
|
|
&& line.contains("BridgeSTWood_Section.3dp"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(
|
|
|line| line.contains("bridge-only two-child class is now grounded save-side")
|
|
&& line.contains("0x0002")
|
|
&& line.contains("BridgeSTWood_Section.3dp")
|
|
)
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line
|
|
.contains("0xff0000ff/0x0001/0xff compact-prefix class is now explicit")
|
|
&& line.contains("dominant prelude=0x0001/0xff"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line
|
|
.contains("0x000055f3/0x0001/0xff compact-prefix class is now explicit")
|
|
&& line.contains("dominant prelude=0x0001/0xff"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line
|
|
.contains("0xff0000ff/0x0002/0xff compact-prefix class is now explicit")
|
|
&& line.contains("dominant prelude="))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("slot +0x44 = 0x004559d0")
|
|
&& line.contains("+0x40 = 0x00455fc0")
|
|
&& line.contains("+0x4c = 0x00455930"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x004559d0 writing 0x55f1")
|
|
&& line.contains("[this+0x206/+0x20a/+0x20e]")
|
|
&& line.contains("0x52ec50")
|
|
&& line.contains("0x55f3"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(
|
|
|line| line.contains("chooser siblings calling 0x00490960 directly")
|
|
&& line.contains("0x0048a340")
|
|
&& line.contains("0x0048f4c0")
|
|
&& line.contains("0x00490200")
|
|
)
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line
|
|
.contains("0x00490960 copying selector fields into the child object")
|
|
&& line.contains("0x00455b70")
|
|
&& line.contains("0x005cfd74")
|
|
&& line.contains("[this+0x248]"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00490200 reading the seeded lanes")
|
|
&& line.contains("0x006cfca8")
|
|
&& line.contains("[this+0x216/+0x218/+0x201/+0x202]"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x0048e140/0x0048e160/0x0048e180")
|
|
&& line.contains("[this+0x206/+0x20a/+0x20e]")
|
|
&& line.contains("0x0048e1a0")
|
|
&& line.contains("[this+0x202]"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x0048ed30")
|
|
&& line.contains("[this+0x248]")
|
|
&& line.contains("[this+0x08]")
|
|
&& line.contains("0x455d20/0x455650/0x53b080"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x004a2eba/0x004a30f9/0x004a339c")
|
|
&& line.contains("0x005cb138 = BallastCapDT_Cap.3dp")
|
|
&& line.contains("0x004909e2"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("mode 0x0b")
|
|
&& line.contains("TrackCapDT/ST_Cap")
|
|
&& line.contains("mode 0x03")
|
|
&& line.contains("OverpassST_section")
|
|
&& line.contains("mode 0x02")
|
|
&& line.contains("mode 0x01"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("objdump on 0x00490960")
|
|
&& line.contains("stem at [esp+0x14]")
|
|
&& line.contains("[esp+0x18]/[esp+0x1c] feed 0x539530")
|
|
&& line.contains("[esp+0x20] feeds 0x53a5b0")
|
|
&& line.contains("[esp+0x34] gates whether the new child is cached")
|
|
&& line.contains("selector-copy block")
|
|
&& line.contains("[esp+0x28]/[esp+0x2c]/[esp+0x30]")
|
|
&& line.contains("0x0048ed01/0x0048ed20")
|
|
&& line.contains("bypass")
|
|
&& line.contains("0x004a17eb/0x004a1995/0x004a1b44/0x004a1b7d")
|
|
&& line.contains("0x004a1b95")
|
|
&& line.contains("arg7/arg8/arg9 = -1/-1/0")
|
|
&& line.contains("arg8 fixed at 1")
|
|
&& line.contains("arg9 fixed at 0"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x004a17eb/0x004a1995")
|
|
&& line.contains("0x621a94/0x621a64")
|
|
&& line.contains("one-bit selector (0 or 1)")
|
|
&& line.contains("0x004a1b44/0x004a1b7d")
|
|
&& line.contains("0x621a9c/0x621a6c")
|
|
&& line.contains("0x004a1b95")
|
|
&& line.contains("0x0048ed01/0x0048ed20")
|
|
&& line.contains("0x005cb198 versus 0x005cb1ac"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("objdump on 0x00455b70")
|
|
&& line.contains("[this+0x206/+0x20a/+0x20e]")
|
|
&& line.contains("0x51d820")
|
|
&& line.contains("0x005c87a8")
|
|
&& line.contains("0x005cfd74 = \"Infrastructure\""))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("objdump on 0x51d820")
|
|
&& line.contains("owned heap strings")
|
|
&& line.contains("0x5a1145")
|
|
&& line.contains("0x5a125d")
|
|
&& line.contains("byte-for-byte"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("objdump on 0x52ec50")
|
|
&& line.contains("bit 5 of [this+0x20]")
|
|
&& line.contains("bit 6 of [this+0x20]")
|
|
&& line.contains("0x531030"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(
|
|
|line| line.contains("objdump on 0x531030/0x5a464d/0x5a44a8")
|
|
&& line.contains("0x531030 just forwards")
|
|
&& line.contains("0x5a44a8 is the shared chunked stream write path")
|
|
)
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(
|
|
|line| line.contains("cannot come from selector-copy state alone")
|
|
&& line.contains("0xff0000ff/0x0001/0xff")
|
|
&& line.contains("dominant TrackCap rows")
|
|
&& line.contains("tunnel residue")
|
|
)
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(
|
|
|line| line.contains("BridgeSTWood_Section.3dp aligns with mode 0x01 Bridge")
|
|
&& line.contains("TunnelSTBrick_Cap/Section.3dp with mode 0x02 Tunnel")
|
|
&& line.contains("BallastCapST_Cap.3dp with mode 0x0a BallastCap")
|
|
&& line.contains("TrackCapST_Cap.3dp with mode 0x0b TrackCap")
|
|
)
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains(
|
|
"mode-family correlations now also split the candidate patterns directly"
|
|
) && line.contains("0x0002/0xff rows=18")
|
|
&& line.contains("bridge"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| {
|
|
line.contains("0x00518140")
|
|
&& line.contains("12-byte row")
|
|
&& line.contains("[collection+0x3c]")
|
|
})
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00518380") && line.contains("ordinal"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x005181f0/0x00518260")
|
|
&& line.contains("previous live id"))
|
|
);
|
|
assert!(trace.notes.iter().any(|line| {
|
|
line.contains("pure bridge-only 0x0002/0xff candidate class is grounded save-side")
|
|
&& line.contains("paired DT/ST siblings at 0x004a2c80 and 0x004a34e0")
|
|
&& line.contains("grounded top-level branch meaning")
|
|
&& line.contains("grounded bridge/tunnel material selector roles")
|
|
&& line.contains("concrete child-construction/write-side chain through 0x00490960")
|
|
&& line.contains("0x004559d0")
|
|
}));
|
|
assert!(trace.notes.iter().any(|line| line.contains("ST-only")
|
|
&& line.contains("ST chooser sibling")
|
|
&& line.contains("DT sibling remains grounded statically")));
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| {
|
|
line.contains("0x52ebd0/0x52ec50")
|
|
&& (line.contains("bits 0x20/0x40") || line.contains("bits 0x20 and 0x40"))
|
|
})
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| {
|
|
(line.contains("0x455870/0x455930")
|
|
|| (line.contains("0x455870") && line.contains("0x455930")))
|
|
&& (line.contains("six 4-byte lanes") || line.contains("six dword lanes"))
|
|
})
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| {
|
|
line.contains("0x530720")
|
|
&& line.contains("0x52e8b0")
|
|
&& line.contains("[this+0x4b/+0x4f/+0x53]")
|
|
})
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[2]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00448a70")
|
|
&& line.contains("[world+0x15e1/+0x162d]")
|
|
&& line.contains("0x00448af0")
|
|
&& line.contains("[world+0x2139/+0x213d/+0x2141]"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[2]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x00493660")
|
|
&& line.contains("[child+0x218]")
|
|
&& line.contains("[child+0x226]")
|
|
&& line.contains("0x006cfc9c")
|
|
&& line.contains("0x487960"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[2]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x0048b660")
|
|
&& line.contains("[child+0x216]")
|
|
&& line.contains("[child+0x201]")
|
|
&& line.contains("0x53a350"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[2]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("0x0048e2c0")
|
|
&& line.contains("bit 0x20 in [child+0x201]")
|
|
&& line.contains("0x53a3a0")
|
|
&& line.contains("0x48a9e0")
|
|
&& line.contains("0x0048e3c0")
|
|
&& line.contains("[child+0x22e]")
|
|
&& line.contains("0x006cfcb4")
|
|
&& line.contains("bit 0x02 in [child+0x24c]"))
|
|
);
|
|
assert!(
|
|
trace.candidate_consumer_hypotheses[0]
|
|
.evidence
|
|
.iter()
|
|
.any(|line| line.contains("u16 child count")
|
|
&& line.contains("saved primary-child byte"))
|
|
);
|
|
assert_eq!(trace.branches[0].status, "grounded_separate_owner_seam");
|
|
assert_eq!(trace.branches[1].status, "disproved_by_grounded_probe");
|
|
}
|
|
}
|