From f52df7d233086499afd34fea65dcbd14d785cbdb Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Fri, 17 Apr 2026 20:49:07 -0700 Subject: [PATCH] Widen save-native company stat-band windows --- README.md | 4 +- crates/rrt-runtime/src/smp.rs | 203 ++++++++++------------------------ docs/README.md | 3 +- docs/runtime-rehost-plan.md | 3 +- 4 files changed, 66 insertions(+), 147 deletions(-) diff --git a/README.md b/README.md index 463479a..f5d7388 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,9 @@ and it now also carries the first grounded stat-band root windows at `[company+0 build on owned state instead of another round of single-field save-offset guesses. The first runtime-side `0x2329` stat-family reader seam is now rehosted too for the currently grounded slots `0x0d` (`current_cash`) and `0x1d` (`book_value_per_share`), so later annual-finance logic can -extend one shared reader family instead of hard-coding more direct field accesses. A checked-in +extend one shared reader family instead of hard-coding more direct field accesses. Those saved +stat-band windows are now widened to 16 dwords per root in save-slice/runtime state so later +year-series finance closure has enough owned raw state to attach to. A checked-in The working rule on the remaining frontier is explicit now too: when a lane is still ambiguous, we should prefer rehosting the owning source state or the real reader/setter family rather than guessing one more derived leaf field from nearby offsets. A checked-in diff --git a/crates/rrt-runtime/src/smp.rs b/crates/rrt-runtime/src/smp.rs index 057241a..7c59646 100644 --- a/crates/rrt-runtime/src/smp.rs +++ b/crates/rrt-runtime/src/smp.rs @@ -3070,27 +3070,27 @@ pub fn inspect_save_company_and_chairman_analysis_bytes( build_save_dword_candidate(&bytes, record_offset, label, *relative_offset) }) .collect::>>()?; - let stat_band_root_0cfb_candidates = - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_CANDIDATE_FIELDS - .iter() - .map(|(label, relative_offset)| { - build_save_dword_candidate(&bytes, record_offset, label, *relative_offset) - }) - .collect::>>()?; - let stat_band_root_0d7f_candidates = - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_CANDIDATE_FIELDS - .iter() - .map(|(label, relative_offset)| { - build_save_dword_candidate(&bytes, record_offset, label, *relative_offset) - }) - .collect::>>()?; - let stat_band_root_1c47_candidates = - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_CANDIDATE_FIELDS - .iter() - .map(|(label, relative_offset)| { - build_save_dword_candidate(&bytes, record_offset, label, *relative_offset) - }) - .collect::>>()?; + let stat_band_root_0cfb_candidates = build_save_company_stat_band_candidates( + &bytes, + record_offset, + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET, + "stat_band_0cfb", + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS, + )?; + let stat_band_root_0d7f_candidates = build_save_company_stat_band_candidates( + &bytes, + record_offset, + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET, + "stat_band_0d7f", + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS, + )?; + let stat_band_root_1c47_candidates = build_save_company_stat_band_candidates( + &bytes, + record_offset, + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET, + "stat_band_1c47", + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS, + )?; entries.push(SmpSaveCompanyRecordAnalysisEntry { company_id, name, @@ -3498,6 +3498,7 @@ const SAVE_COMPANY_RECORD_TRACK_LAYING_CAPACITY_OFFSET: usize = 0x7680; const SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET: usize = 0x0cfb; const SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET: usize = 0x0d7f; const SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET: usize = 0x1c47; +const SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS: usize = 16; const SAVE_COMPANY_RECORD_SCALAR_CANDIDATE_FIELDS: [(&str, usize); 7] = [ ("mutable_support_scalar", 0x4f), ("young_company_support_scalar", 0x57), @@ -3527,108 +3528,6 @@ const SAVE_COMPANY_RECORD_POST_CAPACITY_CANDIDATE_FIELDS: [(&str, usize); 6] = [ ("post_capacity_word_5", 0x7694), ("post_capacity_word_6", 0x7698), ]; -const SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_CANDIDATE_FIELDS: [(&str, usize); 8] = [ - ( - "stat_band_0cfb_word_1", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET, - ), - ( - "stat_band_0cfb_word_2", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET + 4, - ), - ( - "stat_band_0cfb_word_3", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET + 8, - ), - ( - "stat_band_0cfb_word_4", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET + 12, - ), - ( - "stat_band_0cfb_word_5", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET + 16, - ), - ( - "stat_band_0cfb_word_6", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET + 20, - ), - ( - "stat_band_0cfb_word_7", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET + 24, - ), - ( - "stat_band_0cfb_word_8", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET + 28, - ), -]; -const SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_CANDIDATE_FIELDS: [(&str, usize); 8] = [ - ( - "stat_band_0d7f_word_1", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET, - ), - ( - "stat_band_0d7f_word_2", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET + 4, - ), - ( - "stat_band_0d7f_word_3", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET + 8, - ), - ( - "stat_band_0d7f_word_4", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET + 12, - ), - ( - "stat_band_0d7f_word_5", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET + 16, - ), - ( - "stat_band_0d7f_word_6", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET + 20, - ), - ( - "stat_band_0d7f_word_7", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET + 24, - ), - ( - "stat_band_0d7f_word_8", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET + 28, - ), -]; -const SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_CANDIDATE_FIELDS: [(&str, usize); 8] = [ - ( - "stat_band_1c47_word_1", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET, - ), - ( - "stat_band_1c47_word_2", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET + 4, - ), - ( - "stat_band_1c47_word_3", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET + 8, - ), - ( - "stat_band_1c47_word_4", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET + 12, - ), - ( - "stat_band_1c47_word_5", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET + 16, - ), - ( - "stat_band_1c47_word_6", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET + 20, - ), - ( - "stat_band_1c47_word_7", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET + 24, - ), - ( - "stat_band_1c47_word_8", - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET + 28, - ), -]; const SAVE_COMPANY_RECORD_START_SCAN_LIMIT: usize = 0x120; const SAVE_CHAIRMAN_RECORD_NAME_OFFSET: usize = 0x08; @@ -3754,27 +3653,27 @@ fn parse_save_company_roster_probe( bytes, record_offset + SAVE_COMPANY_RECORD_TAKEOVER_COOLDOWN_OFFSET, )?; - let stat_band_root_0cfb_candidates = - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_CANDIDATE_FIELDS - .iter() - .map(|(label, relative_offset)| { - build_save_dword_candidate(bytes, record_offset, label, *relative_offset) - }) - .collect::>>()?; - let stat_band_root_0d7f_candidates = - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_CANDIDATE_FIELDS - .iter() - .map(|(label, relative_offset)| { - build_save_dword_candidate(bytes, record_offset, label, *relative_offset) - }) - .collect::>>()?; - let stat_band_root_1c47_candidates = - SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_CANDIDATE_FIELDS - .iter() - .map(|(label, relative_offset)| { - build_save_dword_candidate(bytes, record_offset, label, *relative_offset) - }) - .collect::>>()?; + let stat_band_root_0cfb_candidates = build_save_company_stat_band_candidates( + bytes, + record_offset, + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0CFB_OFFSET, + "stat_band_0cfb", + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS, + )?; + let stat_band_root_0d7f_candidates = build_save_company_stat_band_candidates( + bytes, + record_offset, + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_0D7F_OFFSET, + "stat_band_0d7f", + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS, + )?; + let stat_band_root_1c47_candidates = build_save_company_stat_band_candidates( + bytes, + record_offset, + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_1C47_OFFSET, + "stat_band_1c47", + SAVE_COMPANY_RECORD_STAT_BAND_ROOT_WINDOW_LEN_DWORDS, + )?; entries.push(SmpLoadedCompanyRosterEntry { company_id, active, @@ -3848,6 +3747,22 @@ fn runtime_company_stat_band_candidate_from_save( } } +fn build_save_company_stat_band_candidates( + bytes: &[u8], + record_offset: usize, + root_offset: usize, + label_prefix: &str, + word_count: usize, +) -> Option> { + (0..word_count) + .map(|index| { + let relative_offset = root_offset.checked_add(index.checked_mul(4)?)?; + let label = format!("{label_prefix}_word_{}", index + 1); + build_save_dword_candidate(bytes, record_offset, &label, relative_offset) + }) + .collect::>>() +} + fn detect_save_company_record_start_offset( bytes: &[u8], header_probe: &SmpSaveTaggedCollectionHeaderProbe, diff --git a/docs/README.md b/docs/README.md index 2eb5675..9b98c67 100644 --- a/docs/README.md +++ b/docs/README.md @@ -116,7 +116,8 @@ The highest-value next passes are now: latches, and the first grounded stat-band root windows at `[company+0x0cfb]`, `[company+0x0d7f]`, and `[company+0x1c47]` for each live company, so later finance/stat-family readers can attach to owned runtime data instead of one more guessed save offset; the first runtime-side `0x2329` - stat-family reader seam is now also rehosted for slots `0x0d` and `0x1d` + stat-family reader seam is now also rehosted for slots `0x0d` and `0x1d`, and the saved + stat-band windows themselves now carry 16 dwords per root - the project rule on the remaining closure work is now explicit too: when one runtime-facing field is still ambiguous, prefer rehosting the owning source state or real reader/setter family first instead of guessing another derived leaf field from neighboring raw offsets diff --git a/docs/runtime-rehost-plan.md b/docs/runtime-rehost-plan.md index 19e578e..315118d 100644 --- a/docs/runtime-rehost-plan.md +++ b/docs/runtime-rehost-plan.md @@ -201,7 +201,8 @@ roster. The current owned company-side roster now includes not just the market/c the first grounded stat-band root windows at `[company+0x0cfb]`, `[company+0x0d7f]`, and `[company+0x1c47]`, and the first runtime-side `0x2329` stat-family reader seam is now rehosted for slots `0x0d` and `0x1d`, so later finance readers can target saved owner state and one shared -reader family directly. +reader family directly. Those stat-band windows now carry 16 dwords per root in the save-slice and +runtime-owned company market state. ## Why This Boundary