Probe save-side region and structure headers

This commit is contained in:
Jan Petykiewicz 2026-04-18 08:10:44 -07:00
commit ddbdddc5ec
5 changed files with 287 additions and 5 deletions

View file

@ -149,6 +149,10 @@ electric-only periodic-company override rewrites the world route-preference byte
company preference, ending it restores the base world byte, and runtime service state now carries
both the active and last applied override instead of treating the route-preference lane as a
reader-only bridge.
Save inspection now also exposes the tagged live region header
`0x5209/0x520a/0x520b` and the tagged placed-structure header `0x36b1/0x36b2/0x36b3` as
first-class owner seams, so the remaining city-connection / linked-transit blocker is record-body
reconstruction rather than missing save-side collection identity.
That same seam now also derives the current live coupon burden directly from owned bond slots, so
later finance service work can consume a runtime reader instead of recomputing from scattered raw
fields.

View file

@ -2545,6 +2545,10 @@ pub struct SmpSaveCompanyChairmanAnalysisReport {
#[serde(default)]
pub world_finance_neighborhood: Option<SmpSaveWorldFinanceNeighborhoodProbe>,
#[serde(default)]
pub region_collection_header: Option<SmpSaveTaggedCollectionHeaderProbe>,
#[serde(default)]
pub placed_structure_collection_header: Option<SmpSaveTaggedCollectionHeaderProbe>,
#[serde(default)]
pub company_entries: Vec<SmpSaveCompanyRecordAnalysisEntry>,
#[serde(default)]
pub chairman_entries: Vec<SmpSaveChairmanRecordAnalysisEntry>,
@ -2803,6 +2807,8 @@ pub struct SmpInspectionReport {
pub save_world_finance_neighborhood_probe: Option<SmpSaveWorldFinanceNeighborhoodProbe>,
pub save_company_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
pub save_chairman_profile_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
pub save_region_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
pub save_placed_structure_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
#[serde(default)]
pub save_company_roster_probe: Option<SmpLoadedCompanyRoster>,
#[serde(default)]
@ -3066,6 +3072,28 @@ pub fn load_save_slice_from_report(
probe.close_tag_offset
));
}
if let Some(probe) = &report.save_region_collection_header_probe {
notes.push(format!(
"Raw save tagged region header reports live_record_count={} and live_id_bound={} with direct_record_stride=0x{:x} at file offsets 0x{:x}/0x{:x}/0x{:x}.",
probe.live_record_count,
probe.live_id_bound,
probe.direct_record_stride,
probe.metadata_tag_offset,
probe.records_tag_offset,
probe.close_tag_offset
));
}
if let Some(probe) = &report.save_placed_structure_collection_header_probe {
notes.push(format!(
"Raw save tagged placed-structure header reports live_record_count={} and live_id_bound={} with serialized stride hint 0x{:x} at file offsets 0x{:x}/0x{:x}/0x{:x}.",
probe.live_record_count,
probe.live_id_bound,
probe.direct_record_stride,
probe.metadata_tag_offset,
probe.records_tag_offset,
probe.close_tag_offset
));
}
if let Some(roster) = &report.save_company_roster_probe {
notes.push(format!(
"Raw save inspection reconstructed {} company direct records from the tagged company collection.",
@ -3415,6 +3443,21 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
.to_string(),
);
}
if let Some(header) = report.save_region_collection_header_probe.as_ref() {
notes.push(format!(
"Region analysis now also exports the tagged region collection header: live_record_count={} live_id_bound={} direct_record_stride=0x{:x}.",
header.live_record_count, header.live_id_bound, header.direct_record_stride
));
}
if let Some(header) = report
.save_placed_structure_collection_header_probe
.as_ref()
{
notes.push(format!(
"Placed-structure analysis now also exports the tagged collection header: live_record_count={} live_id_bound={} serialized_stride_hint=0x{:x}.",
header.live_record_count, header.live_id_bound, header.direct_record_stride
));
}
if !company_entries.is_empty() {
notes.push(
"Company debt is derived from the grounded bond table at [company+0x5b/+0x5f] by summing live principal slots.".to_string(),
@ -3456,6 +3499,10 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
world_issue_37,
world_economic_tuning,
world_finance_neighborhood,
region_collection_header: report.save_region_collection_header_probe.clone(),
placed_structure_collection_header: report
.save_placed_structure_collection_header_probe
.clone(),
company_entries,
chairman_entries,
notes,
@ -7454,6 +7501,17 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> Sm
file_extension_hint.as_deref(),
container_profile.as_ref(),
);
let save_region_collection_header_probe = parse_save_region_collection_header_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
);
let save_placed_structure_collection_header_probe =
parse_save_placed_structure_collection_header_probe(
bytes,
file_extension_hint.as_deref(),
container_profile.as_ref(),
);
let save_company_roster_probe = parse_save_company_roster_probe(
bytes,
save_company_collection_header_probe.as_ref(),
@ -7617,6 +7675,8 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> Sm
save_world_finance_neighborhood_probe,
save_company_collection_header_probe,
save_chairman_profile_collection_header_probe,
save_region_collection_header_probe,
save_placed_structure_collection_header_probe,
save_company_roster_probe,
save_chairman_profile_table_probe,
rt3_105_save_name_table_probe,
@ -9555,6 +9615,65 @@ fn parse_save_chairman_profile_collection_header_probe(
)
}
fn parse_save_region_collection_header_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
parse_save_tagged_collection_header_probe(
bytes,
file_extension_hint,
container_profile,
0x00005209,
0x0000520a,
0x0000520b,
"save-region-tagged-header-counts",
"scenario-save-region-header-counts",
|header| {
header.direct_collection_flag == 1
&& header.direct_record_stride >= 0x100
&& header.direct_record_stride <= 0x400
&& header.live_id_bound >= 0x10
&& header.live_id_bound <= 0x100
&& header.live_record_count >= 1
&& header.live_record_count <= header.live_id_bound
},
vec![
"save-side live region collection shares tagged header family 0x5209/0x520a/0x520b with other indexed direct-record bundles".to_string(),
"the grounded region-side candidate is the smaller direct-record family with stride 0x1d5 and live_id_bound/count in the city-region range, not the larger chairman/profile family".to_string(),
],
)
}
fn parse_save_placed_structure_collection_header_probe(
bytes: &[u8],
file_extension_hint: Option<&str>,
container_profile: Option<&SmpContainerProfile>,
) -> Option<SmpSaveTaggedCollectionHeaderProbe> {
parse_save_tagged_collection_header_probe(
bytes,
file_extension_hint,
container_profile,
0x000036b1,
0x000036b2,
0x000036b3,
"save-placed-structure-tagged-header-counts",
"scenario-save-placed-structure-header-counts",
|header| {
header.direct_collection_flag == 0
&& header.direct_record_stride >= 1
&& header.direct_record_stride <= 0x20
&& header.live_id_bound >= 0x100
&& header.live_record_count >= 0x100
&& header.live_record_count <= header.live_id_bound
},
vec![
"save-side placed-structure collection uses tagged family 0x36b1/0x36b2/0x36b3 beneath the wider local-runtime and route-entry rebuild owners".to_string(),
"current evidence only grounds header-level placed-structure collection counts here; direct record-body reconstruction still needs the later per-entry load/save slot study.".to_string(),
],
)
}
#[derive(Clone, Copy)]
struct IndexedCollectionHeaderSummary {
metadata_tag_offset: usize,
@ -16396,6 +16515,59 @@ mod tests {
],
evidence: vec![],
});
report.save_region_collection_header_probe = Some(SmpSaveTaggedCollectionHeaderProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-region-tagged-header-counts".to_string(),
semantic_family: "scenario-save-region-header-counts".to_string(),
metadata_tag_offset: 0x3000,
records_tag_offset: 0x3100,
close_tag_offset: 0x3200,
direct_collection_flag: 1,
direct_collection_flag_hex: "0x00000001".to_string(),
direct_record_stride: 0x1d5,
direct_record_stride_hex: "0x000001d5".to_string(),
live_id_bound: 0x32,
live_id_bound_hex: "0x00000032".to_string(),
live_record_count: 0x14,
live_record_count_hex: "0x00000014".to_string(),
header_words: vec![1, 0x1d5, 0x32, 0x14, 0x32, 0x14],
header_hex_words: vec![
"0x00000001".to_string(),
"0x000001d5".to_string(),
"0x00000032".to_string(),
"0x00000014".to_string(),
"0x00000032".to_string(),
"0x00000014".to_string(),
],
evidence: vec![],
});
report.save_placed_structure_collection_header_probe =
Some(SmpSaveTaggedCollectionHeaderProbe {
profile_family: "rt3-105-save-container-v1".to_string(),
source_kind: "save-placed-structure-tagged-header-counts".to_string(),
semantic_family: "scenario-save-placed-structure-header-counts".to_string(),
metadata_tag_offset: 0x4000,
records_tag_offset: 0x4100,
close_tag_offset: 0x4200,
direct_collection_flag: 0,
direct_collection_flag_hex: "0x00000000".to_string(),
direct_record_stride: 0x06,
direct_record_stride_hex: "0x00000006".to_string(),
live_id_bound: 0x7ee,
live_id_bound_hex: "0x000007ee".to_string(),
live_record_count: 0x7ea,
live_record_count_hex: "0x000007ea".to_string(),
header_words: vec![0, 6, 0x0a, 0x14, 0x7ee, 0x7ea],
header_hex_words: vec![
"0x00000000".to_string(),
"0x00000006".to_string(),
"0x0000000a".to_string(),
"0x00000014".to_string(),
"0x000007ee".to_string(),
"0x000007ea".to_string(),
],
evidence: vec![],
});
let slice = load_save_slice_from_report(&report).expect("save slice");
@ -16462,6 +16634,15 @@ mod tests {
assert!(slice.notes.iter().any(|note| {
note.contains("tagged chairman/profile header reports live_record_count=2")
}));
assert!(
slice
.notes
.iter()
.any(|note| note.contains("tagged region header reports live_record_count=20"))
);
assert!(slice.notes.iter().any(|note| {
note.contains("tagged placed-structure header reports live_record_count=2026")
}));
}
#[test]
@ -16540,6 +16721,84 @@ mod tests {
assert_eq!(probe.live_record_count, 2);
}
#[test]
fn parses_region_tagged_collection_header_probe_from_exact_u32_tags() {
let mut bytes = vec![0u8; 0x400];
let metadata_tag_offset = 0x40usize;
let records_tag_offset = 0x140usize;
let close_tag_offset = 0x180usize;
bytes[metadata_tag_offset..metadata_tag_offset + 4]
.copy_from_slice(&0x00005209u32.to_le_bytes());
bytes[records_tag_offset..records_tag_offset + 4]
.copy_from_slice(&0x0000520au32.to_le_bytes());
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.to_le_bytes());
let header_words = [
1u32, 0x1d5, 0x32, 0x14, 0x32, 0x14, 0x14, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0,
];
for (index, word) in header_words.into_iter().enumerate() {
let offset = metadata_tag_offset + 4 + index * 4;
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
}
let probe = parse_save_region_collection_header_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
)
.expect("region header probe should parse");
assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
assert_eq!(probe.records_tag_offset, records_tag_offset);
assert_eq!(probe.close_tag_offset, close_tag_offset);
assert_eq!(probe.direct_collection_flag, 1);
assert_eq!(probe.direct_record_stride, 0x1d5);
assert_eq!(probe.live_id_bound, 0x32);
assert_eq!(probe.live_record_count, 0x14);
}
#[test]
fn parses_placed_structure_tagged_collection_header_probe_from_exact_u32_tags() {
let mut bytes = vec![0u8; 0x400];
let metadata_tag_offset = 0x40usize;
let records_tag_offset = 0x140usize;
let close_tag_offset = 0x180usize;
bytes[metadata_tag_offset..metadata_tag_offset + 4]
.copy_from_slice(&0x000036b1u32.to_le_bytes());
bytes[records_tag_offset..records_tag_offset + 4]
.copy_from_slice(&0x000036b2u32.to_le_bytes());
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x000036b3u32.to_le_bytes());
let header_words = [
0u32, 0x06, 0x0a, 0x14, 0x7ee, 0x7ea, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
];
for (index, word) in header_words.into_iter().enumerate() {
let offset = metadata_tag_offset + 4 + index * 4;
bytes[offset..offset + 4].copy_from_slice(&word.to_le_bytes());
}
let probe = parse_save_placed_structure_collection_header_probe(
&bytes,
Some("gms"),
Some(&SmpContainerProfile {
profile_family: "rt3-105-save-container-v1".to_string(),
profile_evidence: vec![],
is_known_profile: true,
}),
)
.expect("placed structure header probe should parse");
assert_eq!(probe.metadata_tag_offset, metadata_tag_offset);
assert_eq!(probe.records_tag_offset, records_tag_offset);
assert_eq!(probe.close_tag_offset, close_tag_offset);
assert_eq!(probe.direct_collection_flag, 0);
assert_eq!(probe.direct_record_stride, 0x06);
assert_eq!(probe.live_id_bound, 0x7ee);
assert_eq!(probe.live_record_count, 0x7ea);
}
#[test]
fn parses_save_company_roster_probe_from_direct_records() {
let metadata_tag_offset = 0x40usize;

View file

@ -191,6 +191,10 @@ The highest-value next passes are now:
electric-only periodic-company override rewrites the world route-preference byte to the
effective company preference, ending it restores the base world byte, and runtime service state
now carries both the active and last applied override
- save inspection now also exposes the tagged live region header `0x5209/0x520a/0x520b` and the
tagged placed-structure header `0x36b1/0x36b2/0x36b3` as first-class owner seams, so the
remaining city-connection / linked-transit blocker is record-body reconstruction rather than
missing save-side collection identity
- 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; the checked-in

View file

@ -9,11 +9,13 @@ Working rule:
## Next
- Rehost the next live branch inside
`company_service_periodic_city_connection_finance_and_linked_transit_lanes`, especially the
city-connection announcement / linked-transit roster-maintenance side that now sits on top of
the owned periodic-service seam and the new route-preference apply/restore mutation seam instead
of loose atlas notes.
- Reconstruct the save-side region record body on top of the newly grounded tagged region header,
especially the pending bonus lane `[region+0x276]`, completion latch `[region+0x302]`, one-shot
notice latch `[region+0x316]`, severity/source lane `[region+0x25e]`, and any stable region-id
or class discriminator that can drive shellless city-connection service.
- Reconstruct the save-side placed-structure collection body on top of the newly grounded
`0x36b1/0x36b2/0x36b3` header seam so the blocked city-connection / linked-transit branch can
stop depending on atlas-only placed-structure and local-runtime refresh notes.
- 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
@ -40,9 +42,18 @@ Working rule:
- Full shell/dialog ownership remains intentionally out of scope.
- Any candidate slice that requires guessing rather than rehosting owning state or real
reader/setter families stays blocked until a better owner seam is grounded.
- The city-connection announcement / linked-transit roster-maintenance branch is still blocked at
the record-body level, not the header level: the runtime now has tagged region and
placed-structure collection header seams, but it does not yet reconstruct the live region or
placed-structure record bodies those service owners need.
## Recently Done
- Save inspection now exposes the tagged region collection header (`0x5209/0x520a/0x520b`,
stride `0x1d5`) and the tagged placed-structure collection header
(`0x36b1/0x36b2/0x36b3`) as first-class owner seams, so the blocked city-connection /
linked-transit queue now has grounded save-side collection counts and ids to build on instead of
only atlas notes.
- Stepped calendar progression now also refreshes save-world owner time fields, including packed
year, packed tuple words, absolute counter, and the derived selected-year gap scalar.
- Automatic year-rollover calendar stepping now invokes periodic-boundary service.

View file

@ -269,6 +269,10 @@ electric-only periodic-company override rewrites `[world+0x4c74]` to the effecti
preference for the active service pass, ending the override restores the base world byte, and
runtime service state now carries both the active and last applied override instead of leaving the
route-preference seam as a pure reader note.
Save inspection now also exposes the tagged live region header `0x5209/0x520a/0x520b` and the
tagged placed-structure header `0x36b1/0x36b2/0x36b3` as first-class owner seams, so the
remaining city-connection / linked-transit blocker is record-body reconstruction rather than
missing save-side collection identity.
That same seam now also carries the fixed-world building-density growth setting plus the linked
chairman personality byte, which is enough to rehost the annual stock-repurchase gate on owned
save/runtime state instead of another threshold-only note. The stock-capital issue branch now