diff --git a/crates/rrt-cli/src/main.rs b/crates/rrt-cli/src/main.rs index f0e993f..cb6eb29 100644 --- a/crates/rrt-cli/src/main.rs +++ b/crates/rrt-cli/src/main.rs @@ -134,6 +134,9 @@ enum Command { RuntimeInspectSaveCompanyChairman { smp_path: PathBuf, }, + RuntimeInspectSavePlacedStructureTriplets { + smp_path: PathBuf, + }, RuntimeCompareRegionFixedRowRuns { left_path: PathBuf, right_path: PathBuf, @@ -904,6 +907,9 @@ fn real_main() -> Result<(), Box> { Command::RuntimeInspectSaveCompanyChairman { smp_path } => { run_runtime_inspect_save_company_chairman(&smp_path)?; } + Command::RuntimeInspectSavePlacedStructureTriplets { smp_path } => { + run_runtime_inspect_save_placed_structure_triplets(&smp_path)?; + } Command::RuntimeCompareRegionFixedRowRuns { left_path, right_path, @@ -1131,6 +1137,13 @@ fn parse_command() -> Result> { smp_path: PathBuf::from(path), }) } + [command, subcommand, path] + if command == "runtime" && subcommand == "inspect-save-placed-structure-triplets" => + { + Ok(Command::RuntimeInspectSavePlacedStructureTriplets { + smp_path: PathBuf::from(path), + }) + } [command, subcommand, left_path, right_path] if command == "runtime" && subcommand == "compare-region-fixed-row-runs" => { @@ -1387,7 +1400,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 compare-region-fixed-row-runs | 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 ]" + "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-placed-structure-triplets | runtime compare-region-fixed-row-runs | 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(), ), } @@ -1628,6 +1641,17 @@ fn run_runtime_inspect_save_company_chairman( Ok(()) } +fn run_runtime_inspect_save_placed_structure_triplets( + smp_path: &Path, +) -> Result<(), Box> { + let analysis = inspect_save_company_and_chairman_analysis_file(smp_path)?; + println!( + "{}", + serde_json::to_string_pretty(&analysis.placed_structure_record_triplets)? + ); + Ok(()) +} + fn run_runtime_compare_region_fixed_row_runs( left_path: &Path, right_path: &Path, diff --git a/crates/rrt-runtime/src/smp.rs b/crates/rrt-runtime/src/smp.rs index fdf03d4..d218ac9 100644 --- a/crates/rrt-runtime/src/smp.rs +++ b/crates/rrt-runtime/src/smp.rs @@ -3611,6 +3611,12 @@ pub struct SmpSavePlacedStructureNonzeroCompanionNamePairSummaryEntry { 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, @@ -3647,6 +3653,9 @@ pub struct SmpPeriodicCompanyServiceTraceReport { pub peer_site_selector_candidate_saved_companion_byte_summaries: Vec, #[serde(default)] + pub peer_site_selector_candidate_saved_policy_trailing_word_summaries: + Vec, + #[serde(default)] pub peer_site_selector_candidate_saved_nonzero_companion_name_pair_summaries: Vec, #[serde(default)] @@ -4428,6 +4437,37 @@ fn summarize_peer_site_selector_candidate_saved_companion_bytes( summaries } +fn summarize_peer_site_selector_candidate_saved_policy_trailing_words( + analysis: &SmpSaveCompanyChairmanAnalysisReport, +) -> Vec { + let Some(triplets) = analysis.placed_structure_record_triplets.as_ref() else { + return Vec::new(); + }; + let mut grouped = BTreeMap::::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::>(); + 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 { @@ -4510,6 +4550,8 @@ fn build_periodic_company_service_trace_report( 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_restore_input_fields = vec![ @@ -4975,7 +5017,7 @@ fn build_periodic_company_service_trace_report( ); 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 {:?}, and dominant pre-footer padding len is {:?}.", + "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, @@ -4985,6 +5027,9 @@ fn build_periodic_company_service_trace_report( 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) @@ -5004,6 +5049,19 @@ fn build_periodic_company_service_trace_report( 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(), ); @@ -5027,6 +5085,7 @@ fn build_periodic_company_service_trace_report( 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_restore_input_fields, peer_site_runtime_input_fields, @@ -28021,6 +28080,15 @@ mod tests { 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, diff --git a/docs/rehost-queue.md b/docs/rehost-queue.md index 5d1a171..679e67b 100644 --- a/docs/rehost-queue.md +++ b/docs/rehost-queue.md @@ -151,6 +151,13 @@ Working rule: it iterates the live placed-structure collection `0x0062b26c`, filters rows through `0x0040c990`, optional owner-company match `[site+0x276]`, and row vtable slot `+0x70`, which keeps that branch on the live application side rather than replay + - the focused save-side triplet probe is now available directly too: + `runtime inspect-save-placed-structure-triplets ` dumps the grounded + `0x36b1/0x55f1/0x55f2/0x55f3` rows without the wider periodic trace wrapper + - real-save output from that focused probe rules the checked-in triplet seam down further: + on grounded `p.gms`, all 2026 rows keep policy trailing word `0x0101`, the profile side is + dominated by companion byte `0x00` with status `unset` (`1885` rows) plus farm-growth buckets + (`138` rows), and the only nonzero companion-byte residue is `3` `TextileMill` rows - the create-side family is grounded separately too: city-connection direct placement already reaches `0x00402cb0 -> 0x00403ed5/0x0040446b -> 0x004134d0 -> 0x0040ef10`