From a669edcaa870c75da9f48d0f9cd6c36f32464f2d Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Sat, 18 Apr 2026 11:50:53 -0700 Subject: [PATCH] Ground placed-structure side-buffer seam --- crates/rrt-cli/src/main.rs | 31 ++++++++++++++++++++++-- crates/rrt-runtime/src/lib.rs | 3 ++- crates/rrt-runtime/src/smp.rs | 45 ++++++++++++++++++++++++++++++++--- docs/rehost-queue.md | 11 +++++++-- 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/crates/rrt-cli/src/main.rs b/crates/rrt-cli/src/main.rs index 062aeac..ff0d46a 100644 --- a/crates/rrt-cli/src/main.rs +++ b/crates/rrt-cli/src/main.rs @@ -27,7 +27,8 @@ 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_smp_file, + inspect_save_company_and_chairman_analysis_file, + inspect_save_placed_structure_dynamic_side_buffer_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, @@ -131,6 +132,9 @@ enum Command { RuntimeInspectSaveCompanyChairman { smp_path: PathBuf, }, + RuntimeInspectPlacedStructureDynamicSideBuffer { + smp_path: PathBuf, + }, RuntimeInspectUnclassifiedSaveCollections { smp_path: PathBuf, }, @@ -857,6 +861,9 @@ fn real_main() -> Result<(), Box> { Command::RuntimeInspectSaveCompanyChairman { smp_path } => { run_runtime_inspect_save_company_chairman(&smp_path)?; } + Command::RuntimeInspectPlacedStructureDynamicSideBuffer { smp_path } => { + run_runtime_inspect_placed_structure_dynamic_side_buffer(&smp_path)?; + } Command::RuntimeInspectUnclassifiedSaveCollections { smp_path } => { run_runtime_inspect_unclassified_save_collections(&smp_path)?; } @@ -1063,6 +1070,14 @@ fn parse_command() -> Result> { smp_path: PathBuf::from(path), }) } + [command, subcommand, path] + if command == "runtime" + && subcommand == "inspect-placed-structure-dynamic-side-buffer" => + { + Ok(Command::RuntimeInspectPlacedStructureDynamicSideBuffer { + smp_path: PathBuf::from(path), + }) + } [command, subcommand, path] if command == "runtime" && subcommand == "inspect-unclassified-save-collections" => { @@ -1273,7 +1288,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-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-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(), ), } @@ -1514,6 +1529,18 @@ fn run_runtime_inspect_save_company_chairman( Ok(()) } +fn run_runtime_inspect_placed_structure_dynamic_side_buffer( + smp_path: &Path, +) -> Result<(), Box> { + println!( + "{}", + serde_json::to_string_pretty(&inspect_save_placed_structure_dynamic_side_buffer_file( + smp_path + )?)? + ); + Ok(()) +} + fn run_runtime_inspect_unclassified_save_collections( smp_path: &Path, ) -> Result<(), Box> { diff --git a/crates/rrt-runtime/src/lib.rs b/crates/rrt-runtime/src/lib.rs index ed1a3e5..d817794 100644 --- a/crates/rrt-runtime/src/lib.rs +++ b/crates/rrt-runtime/src/lib.rs @@ -125,7 +125,8 @@ pub use smp::{ SmpSaveWorldSelectionRoleAnalysis, SmpSaveWorldSelectionRoleAnalysisEntry, SmpSecondaryVariantProbe, SmpSharedHeader, SmpSpecialConditionEntry, SmpSpecialConditionsProbe, inspect_save_company_and_chairman_analysis_bytes, - inspect_save_company_and_chairman_analysis_file, inspect_smp_bytes, inspect_smp_file, + inspect_save_company_and_chairman_analysis_file, + inspect_save_placed_structure_dynamic_side_buffer_file, inspect_smp_bytes, inspect_smp_file, inspect_unclassified_save_collection_headers_file, load_save_slice_file, load_save_slice_from_report, }; diff --git a/crates/rrt-runtime/src/smp.rs b/crates/rrt-runtime/src/smp.rs index 98adb0e..a3f9564 100644 --- a/crates/rrt-runtime/src/smp.rs +++ b/crates/rrt-runtime/src/smp.rs @@ -3126,6 +3126,35 @@ pub fn inspect_unclassified_save_collection_headers_file( )) } +pub fn inspect_save_placed_structure_dynamic_side_buffer_file( + path: &Path, +) -> Result, Box> { + 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_smp_bytes(bytes: &[u8]) -> SmpInspectionReport { inspect_bundle_bytes(bytes, None) } @@ -3286,6 +3315,8 @@ pub fn load_save_slice_from_report( 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 mut notes = summary.notes.clone(); if let Some(probe) = &report.save_world_selection_context_probe { notes.push(format!( @@ -3441,7 +3472,7 @@ pub fn load_save_slice_from_report( probe.entries.first().map(|entry| entry.profile_status_kind.as_str()) )); } - if let Some(probe) = &report.save_placed_structure_dynamic_side_buffer_probe { + if let Some(probe) = &placed_structure_dynamic_side_buffer_probe { notes.push(format!( "Raw save also exposes the separate placed-structure dynamic-side-buffer candidate 0x38a5/0x38a6/0x38a7: live_record_count={}, first compact prefix=({},{},{}), first embedded names={:?}/{:?}, embedded 0x55f1 row count={}.", probe.live_record_count, @@ -3517,8 +3548,16 @@ pub fn inspect_save_company_and_chairman_analysis_bytes( let region_record_triplets = report.save_region_record_triplet_probe.clone(); 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(); + 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 unclassified_tagged_collection_headers = report .save_unclassified_tagged_collection_header_probes .clone(); diff --git a/docs/rehost-queue.md b/docs/rehost-queue.md index d39502e..f8f0134 100644 --- a/docs/rehost-queue.md +++ b/docs/rehost-queue.md @@ -23,8 +23,9 @@ Working rule: semantics of the now-grounded compact `0x55f3` footer dword/status lane and the newly exposed separate tagged side-buffer seam candidates, especially the exact `0x38a5/0x38a6/0x38a7` family whose compact `6`-byte header pattern and embedded placed-structure-style `0x55f1` - name rows now make it the strongest current candidate for the separate placed-structure dynamic - side-buffer owner. + name rows now make it the grounded placed-structure dynamic side-buffer owner; the remaining + blocker is semantic closure of the 6-byte prefix lane and its relation to the embedded + `0x55f1/0x55f2/0x55f3` row subset. - Extend shellless clock advancement so more periodic-company service branches consume owned runtime time state directly instead of only the explicit periodic service command. - Keep widening selected-year world-owner state only when a full owning reader/rebuild family is @@ -90,6 +91,12 @@ Working rule: `rrt-runtime`: its synthetic regression is grounded, its header shape is checked in, and the parser now expects a compact 6-byte prefix plus separator byte before an embedded placed-structure-style dual-name row rather than treating the family as anonymous residue. +- That exact `0x38a5/0x38a6/0x38a7` parser is now also wired through a lightweight CLI inspector + and the normal save company/chairman analysis output, and grounded real saves now prove the + same seam directly: + `q.gms` exposes `live_record_count=3865`, prefix `0x0005d368/0x0001/0xff`, and first embedded + names `TrackCapST_Cap.3dp` / `Infrastructure`; `p.gms` exposes the same structure with + `live_record_count=2467`. - The placed-structure tagged save stream now also exposes repeated `0x55f1/0x55f2/0x55f3` triplets with dual name stems, a fixed five-`f32` policy row, and a compact `0x5dc1...0x5dc2` footer carrying one raw `u32` payload lane plus one live `i32` status lane, so the remaining