diff --git a/README.md b/README.md index c52124e..3216fa3 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,12 @@ guessing one more derived leaf field from nearby offsets, and the checked-in [`docs/rehost-queue.md`](docs/rehost-queue.md) file is now the control surface for that loop: after each commit, check the queue and continue unless the queue is empty, a real blocker remains, or approval is needed. A checked-in +The same runtime surface now also exposes higher-layer blocker probes: +`runtime inspect-periodic-company-service-trace `, +`runtime inspect-region-service-trace `, and +`runtime inspect-infrastructure-asset-trace `, so the next city-connection / +linked-transit slices can start from explicit owner-seam blockers instead of another generic save +scan. A checked-in `EventEffects` export now exists too in `artifacts/exports/rt3-1.06/event-effects-table.json`, and a checked-in semantic closure layer now exists beside it in `artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json`. Recovered diff --git a/crates/rrt-cli/src/main.rs b/crates/rrt-cli/src/main.rs index 18d03c2..0f8c1a9 100644 --- a/crates/rrt-cli/src/main.rs +++ b/crates/rrt-cli/src/main.rs @@ -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> { 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> { 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> { }) } _ => Err( - "usage: rrt-cli [validate [repo-root] | finance eval | finance diff | runtime validate-fixture | runtime summarize-fixture | runtime export-fixture-state | runtime diff-state | runtime summarize-state | runtime import-state | runtime inspect-smp | runtime summarize-save-load | runtime load-save-slice | runtime inspect-save-company-chairman | runtime inspect-save-region-queued-notice-records | runtime inspect-placed-structure-dynamic-side-buffer | runtime inspect-unclassified-save-collections | runtime import-save-state | runtime export-save-slice | runtime export-overlay-import | runtime inspect-pk4 | runtime inspect-cargo-types | runtime inspect-cargo-skins | runtime inspect-cargo-economy-sources | runtime inspect-cargo-production-selector | runtime inspect-cargo-price-selector | runtime inspect-win | runtime extract-pk4-entry | runtime inspect-campaign-exe | runtime compare-classic-profile [saveN.gms...] | runtime compare-105-profile [saveN.gms...] | runtime compare-candidate-table [fileN...] | runtime compare-recipe-book-lines [fileN...] | runtime compare-setup-payload-core [fileN...] | runtime compare-setup-launch-payload [fileN...] | runtime compare-post-special-conditions-scalars [fileN...] | runtime scan-candidate-table-headers | runtime scan-special-conditions | runtime scan-aligned-runtime-rule-band | runtime scan-post-special-conditions-scalars | runtime scan-post-special-conditions-tail | runtime scan-recipe-book-lines | runtime export-profile-block ]" + "usage: rrt-cli [validate [repo-root] | finance eval | finance diff | runtime validate-fixture | runtime summarize-fixture | runtime export-fixture-state | runtime diff-state | runtime summarize-state | runtime import-state | runtime inspect-smp | runtime summarize-save-load | runtime load-save-slice | runtime inspect-save-company-chairman | runtime inspect-periodic-company-service-trace | runtime inspect-region-service-trace | runtime inspect-infrastructure-asset-trace | runtime inspect-save-region-queued-notice-records | runtime inspect-placed-structure-dynamic-side-buffer | runtime inspect-unclassified-save-collections | runtime import-save-state | runtime export-save-slice | runtime export-overlay-import | runtime inspect-pk4 | runtime inspect-cargo-types | runtime inspect-cargo-skins | runtime inspect-cargo-economy-sources | runtime inspect-cargo-production-selector | runtime inspect-cargo-price-selector | runtime inspect-win | runtime extract-pk4-entry | runtime inspect-campaign-exe | runtime compare-classic-profile [saveN.gms...] | runtime compare-105-profile [saveN.gms...] | runtime compare-candidate-table [fileN...] | runtime compare-recipe-book-lines [fileN...] | runtime compare-setup-payload-core [fileN...] | runtime compare-setup-launch-payload [fileN...] | runtime compare-post-special-conditions-scalars [fileN...] | runtime scan-candidate-table-headers | runtime scan-special-conditions | runtime scan-aligned-runtime-rule-band | runtime scan-post-special-conditions-scalars | runtime scan-post-special-conditions-tail | runtime scan-recipe-book-lines | runtime export-profile-block ]" .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> { + 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> { + 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> { + 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> { diff --git a/crates/rrt-runtime/src/lib.rs b/crates/rrt-runtime/src/lib.rs index 97e65e6..fd29ea5 100644 --- a/crates/rrt-runtime/src/lib.rs +++ b/crates/rrt-runtime/src/lib.rs @@ -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; diff --git a/crates/rrt-runtime/src/smp.rs b/crates/rrt-runtime/src/smp.rs index b4896be..7e6fe94 100644 --- a/crates/rrt-runtime/src/smp.rs +++ b/crates/rrt-runtime/src/smp.rs @@ -2882,6 +2882,101 @@ pub struct SmpSaveCompanyChairmanAnalysisReport { pub notes: Vec, } +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SmpServiceTraceBranchStatus { + pub branch_name: String, + pub status: String, + #[serde(default)] + pub grounded_inputs: Vec, + #[serde(default)] + pub blocking_inputs: Vec, + #[serde(default)] + pub notes: Vec, +} + +#[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, + pub preferred_locomotive_engine_type_raw_u8: u8, + pub city_connection_latch: bool, + pub linked_transit_latch: bool, + #[serde(default)] + pub branches: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SmpPeriodicCompanyServiceTraceReport { + pub profile_family: String, + #[serde(default)] + pub selected_company_id: Option, + #[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, + #[serde(default)] + pub notes: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SmpRegionServiceTraceEntry { + pub name: String, + #[serde(default)] + pub profile_collection_count: Option, + 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, +} + +#[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, + #[serde(default)] + pub notes: Vec, +} + +#[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, + #[serde(default)] + pub notes: Vec, +} + #[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> { + 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::>(); + + 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> { + 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> { + 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::>() + }) + .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"); + } } diff --git a/docs/README.md b/docs/README.md index 2183b93..12ca437 100644 --- a/docs/README.md +++ b/docs/README.md @@ -203,6 +203,12 @@ The highest-value next passes are now: `docs/rehost-queue.md` file is the control surface for that work loop, and after each commit the next queue item should run unless the queue is empty, a real blocker remains, or approval is needed +- `rrt-runtime` now also exposes higher-layer probe commands for the current blocked frontier: + `runtime inspect-periodic-company-service-trace `, + `runtime inspect-region-service-trace `, and + `runtime inspect-infrastructure-asset-trace `. These reports make the current + shellless city-connection / linked-transit blockers explicit as missing owner seams rather than + generic “still unresolved” residue. - a checked-in `EventEffects` export now exists at `artifacts/exports/rt3-1.06/event-effects-table.json`, and a checked-in semantic closure layer now exists at `artifacts/exports/rt3-1.06/event-effects-semantic-catalog.json` diff --git a/docs/rehost-queue.md b/docs/rehost-queue.md index ca43874..29ea111 100644 --- a/docs/rehost-queue.md +++ b/docs/rehost-queue.md @@ -9,6 +9,11 @@ Working rule: ## Next +- Follow the new higher-layer probe outputs instead of another blind save scan: + `runtime inspect-infrastructure-asset-trace ` now shows that the `0x38a5` + infrastructure-asset seam is grounded and the old alias hypothesis is disproved on `q.gms`, so + the next placed-structure slice should target the consumer mapping above that seam rather than + more collection discovery. - Reconstruct the save-side region record body on top of the newly corrected non-direct tagged region seam (`0x5209/0x520a/0x520b`, stride hint `0x06`, `Marker09` record stems) now that the `0x55f3` payload is known to be fully consumed by the embedded profile collection on grounded @@ -65,6 +70,18 @@ Working rule: ## Recently Done +- `rrt-runtime` now exposes three higher-layer probe surfaces and matching CLI inspectors: + `runtime inspect-periodic-company-service-trace `, + `runtime inspect-region-service-trace `, and + `runtime inspect-infrastructure-asset-trace `. These reports separate grounded outer + owner inputs, runnable shellless branches, and explicit missing owner seams instead of leaving + the current city-connection / linked-transit frontier as an opaque blocker. +- Those same probes now also sharpen the next queue choice on grounded real saves: the periodic + company outer owner shows annual finance and route-preference override as grounded shellless + branches while city-connection and linked-transit stay blocked on region/infrastructure owner + seams; the region trace keeps the queued kind-`7` notice family on the transient side; and the + infrastructure trace now makes the `0x38a5` consumer-mapping blocker first-class after + disproving any alias to the `0x36b1` placed-structure triplet corpus. - Save inspection now splits the shared `0x5209/0x520a/0x520b` family correctly: the smaller direct `0x1d5` collection is the live train family and now exposes a live-entry directory rooted at metadata dword `16`, while the actual region family is the larger non-direct `Marker09` diff --git a/docs/runtime-rehost-plan.md b/docs/runtime-rehost-plan.md index 6d014bd..7daaf57 100644 --- a/docs/runtime-rehost-plan.md +++ b/docs/runtime-rehost-plan.md @@ -297,6 +297,12 @@ The process rule for the remaining runtime work is explicit too: prefer rehostin real reader/setter families over guessing leaf fields, and use `docs/rehost-queue.md` as the checked-in control surface for the work loop. After each commit, check that queue and continue unless the queue is empty, a real blocker remains, or approval is needed. +The same control surface now also has matching runtime probes: +`runtime inspect-periodic-company-service-trace `, +`runtime inspect-region-service-trace `, and +`runtime inspect-infrastructure-asset-trace `. Use those first when the next blocked +frontier is “which higher-layer owner seam is still missing?” rather than “which raw save +collection exists?”. That same owner seam now also derives live coupon burden totals directly from saved bond slots, which gives later finance service work a bounded runtime reader instead of another synthetic finance leaf.