Add higher-layer rehost trace probes

This commit is contained in:
Jan Petykiewicz 2026-04-18 12:38:05 -07:00
commit 6bfe4d043f
7 changed files with 741 additions and 22 deletions

View file

@ -27,10 +27,11 @@ use rrt_runtime::{
SmpRt3105PackedProfileBlock, SmpSaveLoadSummary, WinInspectionReport, execute_step_command,
extract_pk4_entry_file, inspect_campaign_exe_file, inspect_cargo_economy_sources_with_bindings,
inspect_cargo_skin_pk4, inspect_cargo_types_dir, inspect_pk4_file,
inspect_save_company_and_chairman_analysis_file,
inspect_save_company_and_chairman_analysis_file, inspect_save_infrastructure_asset_trace_file,
inspect_save_periodic_company_service_trace_file,
inspect_save_placed_structure_dynamic_side_buffer_file,
inspect_save_region_queued_notice_records_file, inspect_smp_file,
inspect_unclassified_save_collection_headers_file, inspect_win_file,
inspect_save_region_queued_notice_records_file, inspect_save_region_service_trace_file,
inspect_smp_file, inspect_unclassified_save_collection_headers_file, inspect_win_file,
load_runtime_snapshot_document, load_runtime_state_import, load_save_slice_file,
project_save_slice_to_runtime_state_import, save_runtime_overlay_import_document,
save_runtime_save_slice_document, save_runtime_snapshot_document,
@ -133,6 +134,15 @@ enum Command {
RuntimeInspectSaveCompanyChairman {
smp_path: PathBuf,
},
RuntimeInspectPeriodicCompanyServiceTrace {
smp_path: PathBuf,
},
RuntimeInspectRegionServiceTrace {
smp_path: PathBuf,
},
RuntimeInspectInfrastructureAssetTrace {
smp_path: PathBuf,
},
RuntimeInspectSaveRegionQueuedNoticeRecords {
smp_path: PathBuf,
},
@ -294,6 +304,24 @@ struct RuntimeSaveCompanyChairmanAnalysisOutput {
analysis: rrt_runtime::SmpSaveCompanyChairmanAnalysisReport,
}
#[derive(Debug, Serialize)]
struct RuntimePeriodicCompanyServiceTraceOutput {
path: String,
trace: rrt_runtime::SmpPeriodicCompanyServiceTraceReport,
}
#[derive(Debug, Serialize)]
struct RuntimeRegionServiceTraceOutput {
path: String,
trace: rrt_runtime::SmpRegionServiceTraceReport,
}
#[derive(Debug, Serialize)]
struct RuntimeInfrastructureAssetTraceOutput {
path: String,
trace: rrt_runtime::SmpInfrastructureAssetTraceReport,
}
#[derive(Debug, Serialize)]
struct RuntimeSaveSliceExportOutput {
path: String,
@ -865,6 +893,15 @@ fn real_main() -> Result<(), Box<dyn std::error::Error>> {
Command::RuntimeInspectSaveCompanyChairman { smp_path } => {
run_runtime_inspect_save_company_chairman(&smp_path)?;
}
Command::RuntimeInspectPeriodicCompanyServiceTrace { smp_path } => {
run_runtime_inspect_periodic_company_service_trace(&smp_path)?;
}
Command::RuntimeInspectRegionServiceTrace { smp_path } => {
run_runtime_inspect_region_service_trace(&smp_path)?;
}
Command::RuntimeInspectInfrastructureAssetTrace { smp_path } => {
run_runtime_inspect_infrastructure_asset_trace(&smp_path)?;
}
Command::RuntimeInspectSaveRegionQueuedNoticeRecords { smp_path } => {
run_runtime_inspect_save_region_queued_notice_records(&smp_path)?;
}
@ -1077,6 +1114,28 @@ fn parse_command() -> Result<Command, Box<dyn std::error::Error>> {
smp_path: PathBuf::from(path),
})
}
[command, subcommand, path]
if command == "runtime"
&& subcommand == "inspect-periodic-company-service-trace" =>
{
Ok(Command::RuntimeInspectPeriodicCompanyServiceTrace {
smp_path: PathBuf::from(path),
})
}
[command, subcommand, path]
if command == "runtime" && subcommand == "inspect-region-service-trace" =>
{
Ok(Command::RuntimeInspectRegionServiceTrace {
smp_path: PathBuf::from(path),
})
}
[command, subcommand, path]
if command == "runtime" && subcommand == "inspect-infrastructure-asset-trace" =>
{
Ok(Command::RuntimeInspectInfrastructureAssetTrace {
smp_path: PathBuf::from(path),
})
}
[command, subcommand, path]
if command == "runtime"
&& subcommand == "inspect-save-region-queued-notice-records" =>
@ -1303,7 +1362,7 @@ fn parse_command() -> Result<Command, Box<dyn std::error::Error>> {
})
}
_ => Err(
"usage: rrt-cli [validate [repo-root] | finance eval <snapshot.json> | finance diff <left.json> <right.json> | runtime validate-fixture <fixture.json> | runtime summarize-fixture <fixture.json> | runtime export-fixture-state <fixture.json> <snapshot.json> | runtime diff-state <left.json> <right.json> | runtime summarize-state <snapshot.json> | runtime import-state <input.json> <snapshot.json> | runtime inspect-smp <file.smp> | runtime summarize-save-load <file.smp> | runtime load-save-slice <file.smp> | runtime inspect-save-company-chairman <file.smp> | runtime inspect-save-region-queued-notice-records <file.smp> | runtime inspect-placed-structure-dynamic-side-buffer <file.smp> | runtime inspect-unclassified-save-collections <file.smp> | runtime import-save-state <file.smp> <snapshot.json> | runtime export-save-slice <file.smp> <save-slice.json> | runtime export-overlay-import <snapshot.json> <save-slice.json> <overlay-import.json> | runtime inspect-pk4 <file.pk4> | runtime inspect-cargo-types <CargoTypes-dir> | runtime inspect-cargo-skins <Cargo106.PK4> | runtime inspect-cargo-economy-sources <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-cargo-production-selector <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-cargo-price-selector <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-win <file.win> | runtime extract-pk4-entry <file.pk4> <entry-name> <output-path> | runtime inspect-campaign-exe <RT3.exe> | runtime compare-classic-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-105-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-candidate-table <file1> <file2> [fileN...] | runtime compare-recipe-book-lines <file1> <file2> [fileN...] | runtime compare-setup-payload-core <file1> <file2> [fileN...] | runtime compare-setup-launch-payload <file1> <file2> [fileN...] | runtime compare-post-special-conditions-scalars <file1> <file2> [fileN...] | runtime scan-candidate-table-headers <root-dir> | runtime scan-special-conditions <root-dir> | runtime scan-aligned-runtime-rule-band <root-dir> | runtime scan-post-special-conditions-scalars <root-dir> | runtime scan-post-special-conditions-tail <root-dir> | runtime scan-recipe-book-lines <root-dir> | runtime export-profile-block <save.gms> <profile.json>]"
"usage: rrt-cli [validate [repo-root] | finance eval <snapshot.json> | finance diff <left.json> <right.json> | runtime validate-fixture <fixture.json> | runtime summarize-fixture <fixture.json> | runtime export-fixture-state <fixture.json> <snapshot.json> | runtime diff-state <left.json> <right.json> | runtime summarize-state <snapshot.json> | runtime import-state <input.json> <snapshot.json> | runtime inspect-smp <file.smp> | runtime summarize-save-load <file.smp> | runtime load-save-slice <file.smp> | runtime inspect-save-company-chairman <file.smp> | runtime inspect-periodic-company-service-trace <file.smp> | runtime inspect-region-service-trace <file.smp> | runtime inspect-infrastructure-asset-trace <file.smp> | runtime inspect-save-region-queued-notice-records <file.smp> | runtime inspect-placed-structure-dynamic-side-buffer <file.smp> | runtime inspect-unclassified-save-collections <file.smp> | runtime import-save-state <file.smp> <snapshot.json> | runtime export-save-slice <file.smp> <save-slice.json> | runtime export-overlay-import <snapshot.json> <save-slice.json> <overlay-import.json> | runtime inspect-pk4 <file.pk4> | runtime inspect-cargo-types <CargoTypes-dir> | runtime inspect-cargo-skins <Cargo106.PK4> | runtime inspect-cargo-economy-sources <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-cargo-production-selector <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-cargo-price-selector <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-win <file.win> | runtime extract-pk4-entry <file.pk4> <entry-name> <output-path> | runtime inspect-campaign-exe <RT3.exe> | runtime compare-classic-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-105-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-candidate-table <file1> <file2> [fileN...] | runtime compare-recipe-book-lines <file1> <file2> [fileN...] | runtime compare-setup-payload-core <file1> <file2> [fileN...] | runtime compare-setup-launch-payload <file1> <file2> [fileN...] | runtime compare-post-special-conditions-scalars <file1> <file2> [fileN...] | runtime scan-candidate-table-headers <root-dir> | runtime scan-special-conditions <root-dir> | runtime scan-aligned-runtime-rule-band <root-dir> | runtime scan-post-special-conditions-scalars <root-dir> | runtime scan-post-special-conditions-tail <root-dir> | runtime scan-recipe-book-lines <root-dir> | runtime export-profile-block <save.gms> <profile.json>]"
.into(),
),
}
@ -1544,6 +1603,39 @@ fn run_runtime_inspect_save_company_chairman(
Ok(())
}
fn run_runtime_inspect_periodic_company_service_trace(
smp_path: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
let report = RuntimePeriodicCompanyServiceTraceOutput {
path: smp_path.display().to_string(),
trace: inspect_save_periodic_company_service_trace_file(smp_path)?,
};
println!("{}", serde_json::to_string_pretty(&report)?);
Ok(())
}
fn run_runtime_inspect_region_service_trace(
smp_path: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
let report = RuntimeRegionServiceTraceOutput {
path: smp_path.display().to_string(),
trace: inspect_save_region_service_trace_file(smp_path)?,
};
println!("{}", serde_json::to_string_pretty(&report)?);
Ok(())
}
fn run_runtime_inspect_infrastructure_asset_trace(
smp_path: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
let report = RuntimeInfrastructureAssetTraceOutput {
path: smp_path.display().to_string(),
trace: inspect_save_infrastructure_asset_trace_file(smp_path)?,
};
println!("{}", serde_json::to_string_pretty(&report)?);
Ok(())
}
fn run_runtime_inspect_save_region_queued_notice_records(
smp_path: &Path,
) -> Result<(), Box<dyn std::error::Error>> {

View file

@ -96,7 +96,7 @@ pub use smp::{
SMP_FOUR_SIDECAR_BYTE_PLANES_MIN_BUNDLE_VERSION, SmpAlignedRuntimeRuleBandLane,
SmpAlignedRuntimeRuleBandProbe, SmpAsciiPreview, SmpClassicPackedProfileBlock,
SmpClassicRehydrateProfileProbe, SmpContainerProfile, SmpEarlyContentProbe,
SmpHeaderVariantProbe, SmpInspectionReport, SmpKnownTagHit,
SmpHeaderVariantProbe, SmpInfrastructureAssetTraceReport, SmpInspectionReport, SmpKnownTagHit,
SmpLoadedCandidateAvailabilityTable, SmpLoadedCargoCatalog, SmpLoadedCargoCatalogEntry,
SmpLoadedChairmanProfileEntry, SmpLoadedChairmanProfileTable, SmpLoadedCompanyRoster,
SmpLoadedCompanyRosterEntry, SmpLoadedEventRuntimeCollectionSummary,
@ -108,31 +108,33 @@ pub use smp::{
SmpLoadedWorldFinanceNeighborhoodState, SmpLoadedWorldIssue37State,
SmpLocomotivePolicyFieldObservation, SmpLocomotivePolicyFloatAlignmentCandidate,
SmpLocomotivePolicyNeighborhoodProbe, SmpPackedProfileWordLane,
SmpPostSpecialConditionsScalarLane, SmpPostSpecialConditionsScalarProbe,
SmpPostTextFieldNeighborhoodProbe, SmpPostTextFloatAlignmentCandidate,
SmpPostTextGroundedFieldObservation, SmpPreRecipeScalarPlateauLane,
SmpPreRecipeScalarPlateauProbe, SmpPreamble, SmpPreambleWord, SmpRecipeBookLineSummary,
SmpRecipeBookSummaryBook, SmpRecipeBookSummaryProbe, SmpRt3105PackedProfileBlock,
SmpRt3105PackedProfileProbe, SmpRt3105PostSpanBridgeProbe, SmpRt3105SaveBridgePayloadProbe,
SmpRt3105SaveNameTableEntry, SmpRt3105SaveNameTableProbe, SmpRuntimeAnchorCycleBlock,
SmpRuntimePostSpanHeaderCandidate, SmpRuntimePostSpanProbe, SmpRuntimeTrailerBlock,
SmpSaveAnchorRunBlock, SmpSaveBootstrapBlock, SmpSaveChairmanRecordAnalysisEntry,
SmpSaveCompanyChairmanAnalysisReport, SmpSaveCompanyRecordAnalysisEntry, SmpSaveDwordCandidate,
SmpSaveLoadCandidateTableSummary, SmpSaveLoadSummary,
SmpSavePlacedStructureDynamicSideBufferAlignmentProbe,
SmpPeriodicCompanyServiceTraceReport, SmpPostSpecialConditionsScalarLane,
SmpPostSpecialConditionsScalarProbe, SmpPostTextFieldNeighborhoodProbe,
SmpPostTextFloatAlignmentCandidate, SmpPostTextGroundedFieldObservation,
SmpPreRecipeScalarPlateauLane, SmpPreRecipeScalarPlateauProbe, SmpPreamble, SmpPreambleWord,
SmpRecipeBookLineSummary, SmpRecipeBookSummaryBook, SmpRecipeBookSummaryProbe,
SmpRegionServiceTraceReport, SmpRt3105PackedProfileBlock, SmpRt3105PackedProfileProbe,
SmpRt3105PostSpanBridgeProbe, SmpRt3105SaveBridgePayloadProbe, SmpRt3105SaveNameTableEntry,
SmpRt3105SaveNameTableProbe, SmpRuntimeAnchorCycleBlock, SmpRuntimePostSpanHeaderCandidate,
SmpRuntimePostSpanProbe, SmpRuntimeTrailerBlock, SmpSaveAnchorRunBlock, SmpSaveBootstrapBlock,
SmpSaveChairmanRecordAnalysisEntry, SmpSaveCompanyChairmanAnalysisReport,
SmpSaveCompanyRecordAnalysisEntry, SmpSaveDwordCandidate, SmpSaveLoadCandidateTableSummary,
SmpSaveLoadSummary, SmpSavePlacedStructureDynamicSideBufferAlignmentProbe,
SmpSavePlacedStructureDynamicSideBufferNamePairSummary,
SmpSavePlacedStructureDynamicSideBufferPrefixPatternSummary,
SmpSavePlacedStructureDynamicSideBufferProbe, SmpSaveRegionQueuedNoticeRecordProbe,
SmpSaveScalarCandidate, SmpSaveTaggedCollectionHeaderProbe, SmpSaveWorldEconomicTuningProbe,
SmpSaveWorldFinanceNeighborhoodProbe, SmpSaveWorldIssue37Probe,
SmpSaveWorldSelectionRoleAnalysis, SmpSaveWorldSelectionRoleAnalysisEntry,
SmpSecondaryVariantProbe, SmpSharedHeader, SmpSpecialConditionEntry, SmpSpecialConditionsProbe,
SmpSecondaryVariantProbe, SmpServiceTraceBranchStatus, SmpSharedHeader,
SmpSpecialConditionEntry, SmpSpecialConditionsProbe,
inspect_save_company_and_chairman_analysis_bytes,
inspect_save_company_and_chairman_analysis_file,
inspect_save_company_and_chairman_analysis_file, inspect_save_infrastructure_asset_trace_file,
inspect_save_periodic_company_service_trace_file,
inspect_save_placed_structure_dynamic_side_buffer_file,
inspect_save_region_queued_notice_records_file, inspect_smp_bytes, inspect_smp_file,
inspect_unclassified_save_collection_headers_file, load_save_slice_file,
load_save_slice_from_report,
inspect_save_region_queued_notice_records_file, inspect_save_region_service_trace_file,
inspect_smp_bytes, inspect_smp_file, inspect_unclassified_save_collection_headers_file,
load_save_slice_file, load_save_slice_from_report,
};
pub use step::{BoundaryEvent, ServiceEvent, StepCommand, StepResult, execute_step_command};
pub use summary::RuntimeSummary;

View file

@ -2882,6 +2882,101 @@ pub struct SmpSaveCompanyChairmanAnalysisReport {
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 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,
#[serde(default)]
pub branches: Vec<SmpServiceTraceBranchStatus>,
}
#[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,
#[serde(default)]
pub companies: Vec<SmpPeriodicCompanyServiceTraceEntry>,
#[serde(default)]
pub notes: Vec<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 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 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 triplet_alignment_overlap_count: usize,
#[serde(default)]
pub branches: Vec<SmpServiceTraceBranchStatus>,
#[serde(default)]
pub notes: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct SmpLoadedSpecialConditionsTable {
pub source_kind: String,
@ -3315,6 +3410,356 @@ pub fn inspect_save_region_queued_notice_records_file(
))
}
fn build_service_trace_branch_status(
branch_name: &str,
status: &str,
grounded_inputs: &[&str],
blocking_inputs: &[&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(),
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 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 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",
],
&[],
&[
"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",
],
&[],
&[
"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",
],
&[
"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_infrastructure_asset_consumer_mapping",
&[
"company linked-transit latch",
"route-preference override seam",
],
&[
"placed-structure record-body semantics",
"0x38a5 infrastructure-asset consumer mapping",
],
&[
"The save side now grounds the owner seams, but not yet the higher-layer consumer that turns them into roster or route actions.",
],
));
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",
],
&[
"The outer owner is bounded, but the concrete candidate/peer scan is not yet rehosted.",
],
));
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,
branches,
}
})
.collect::<Vec<_>>();
let mut notes = Vec::new();
let _ = inspection;
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(),
);
if region_record_body_present || placed_structure_record_body_present {
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(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,
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 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),
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",
],
&["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",
],
&["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(
"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(),
);
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(),
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 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"]
},
&[
"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",
],
&[
"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",
],
&[
"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, consumer semantics are still blocked.".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(),
triplet_alignment_overlap_count: alignment
.map(|probe| probe.overlapping_name_pair_count)
.unwrap_or_default(),
branches,
notes,
}
}
pub fn inspect_smp_bytes(bytes: &[u8]) -> SmpInspectionReport {
inspect_bundle_bytes(bytes, None)
}
@ -21058,4 +21503,149 @@ mod tests {
assert_eq!(alt_profile.profile_family, "rt3-105-alt-map-container-v1");
assert!(alt_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,
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 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(),
name_tag_relative_offset: 0,
policy_tag_relative_offset: 0,
profile_tag_relative_offset: 0,
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_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_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"
);
}
#[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(),
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,
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()),
embedded_name_row_samples: Vec::new(),
compact_prefix_pattern_summaries: Vec::new(),
name_pair_summaries: 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.branches[0].status, "grounded_separate_owner_seam");
assert_eq!(trace.branches[1].status, "disproved_by_grounded_probe");
}
}