Expose save-side region live directory
This commit is contained in:
parent
ddbdddc5ec
commit
fa25433216
5 changed files with 265 additions and 22 deletions
|
|
@ -150,9 +150,10 @@ company preference, ending it restores the base world byte, and runtime service
|
|||
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.
|
||||
`0x5209/0x520a/0x520b` plus its live-entry directory rooted at metadata dword `16`, 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.
|
||||
|
|
|
|||
|
|
@ -150,6 +150,8 @@ const EVENT_RUNTIME_COLLECTION_PACKED_STATE_VERSION: u32 = 0x000003e9;
|
|||
const INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT: usize = 19;
|
||||
const INDEXED_COLLECTION_SERIALIZED_HEADER_LEN: usize =
|
||||
INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT * 4;
|
||||
const SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX: usize = 16;
|
||||
const SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT: usize = 3;
|
||||
const PACKED_EVENT_RECORDS_SYNTHETIC_MAGIC: &[u8; 8] = b"RPEVT001";
|
||||
const PACKED_EVENT_RECORD_SYNTHETIC_MAGIC: &[u8; 4] = b"RPE1";
|
||||
const PACKED_EVENT_RECORD_TEMPLATE_SYNTHETIC_MAGIC: &[u8; 4] = b"RPT1";
|
||||
|
|
@ -1611,6 +1613,38 @@ pub struct SmpSaveTaggedCollectionHeaderProbe {
|
|||
pub evidence: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpSaveRegionCollectionDirectoryEntryProbe {
|
||||
pub live_entry_id: u32,
|
||||
pub payload_relative_offset: u32,
|
||||
pub payload_relative_offset_hex: String,
|
||||
pub payload_absolute_offset: usize,
|
||||
pub previous_live_entry_id: u32,
|
||||
pub previous_live_entry_id_hex: String,
|
||||
pub next_live_entry_id: u32,
|
||||
pub next_live_entry_id_hex: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpSaveRegionCollectionDirectoryProbe {
|
||||
pub profile_family: String,
|
||||
pub source_kind: String,
|
||||
pub semantic_family: String,
|
||||
pub metadata_tag_offset: usize,
|
||||
pub records_tag_offset: usize,
|
||||
pub close_tag_offset: usize,
|
||||
pub directory_root_dword_index: usize,
|
||||
pub directory_entry_dword_count: usize,
|
||||
pub live_record_count: u32,
|
||||
pub live_id_bound: u32,
|
||||
#[serde(default)]
|
||||
pub chain_head_live_entry_id: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub chain_tail_live_entry_id: Option<u32>,
|
||||
pub entries: Vec<SmpSaveRegionCollectionDirectoryEntryProbe>,
|
||||
pub evidence: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct SmpRt3105SaveNameTableProbe {
|
||||
pub profile_family: String,
|
||||
|
|
@ -2547,6 +2581,8 @@ pub struct SmpSaveCompanyChairmanAnalysisReport {
|
|||
#[serde(default)]
|
||||
pub region_collection_header: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
||||
#[serde(default)]
|
||||
pub region_collection_directory: Option<SmpSaveRegionCollectionDirectoryProbe>,
|
||||
#[serde(default)]
|
||||
pub placed_structure_collection_header: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
||||
#[serde(default)]
|
||||
pub company_entries: Vec<SmpSaveCompanyRecordAnalysisEntry>,
|
||||
|
|
@ -2808,6 +2844,7 @@ pub struct SmpInspectionReport {
|
|||
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_region_collection_directory_probe: Option<SmpSaveRegionCollectionDirectoryProbe>,
|
||||
pub save_placed_structure_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
||||
#[serde(default)]
|
||||
pub save_company_roster_probe: Option<SmpLoadedCompanyRoster>,
|
||||
|
|
@ -3083,6 +3120,15 @@ pub fn load_save_slice_from_report(
|
|||
probe.close_tag_offset
|
||||
));
|
||||
}
|
||||
if let Some(probe) = &report.save_region_collection_directory_probe {
|
||||
notes.push(format!(
|
||||
"Raw save tagged region metadata also exposes a live-entry directory with {} entries rooted at metadata dword {} (head={:?}, tail={:?}).",
|
||||
probe.entries.len(),
|
||||
probe.directory_root_dword_index,
|
||||
probe.chain_head_live_entry_id,
|
||||
probe.chain_tail_live_entry_id
|
||||
));
|
||||
}
|
||||
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}.",
|
||||
|
|
@ -3154,6 +3200,7 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
|||
let world_issue_37 = report.save_world_issue_37_probe.clone();
|
||||
let world_economic_tuning = report.save_world_economic_tuning_probe.clone();
|
||||
let world_finance_neighborhood = report.save_world_finance_neighborhood_probe.clone();
|
||||
let region_collection_directory = report.save_region_collection_directory_probe.clone();
|
||||
let company_header_probe = report.save_company_collection_header_probe.as_ref();
|
||||
let chairman_header_probe = report
|
||||
.save_chairman_profile_collection_header_probe
|
||||
|
|
@ -3449,6 +3496,15 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
|||
header.live_record_count, header.live_id_bound, header.direct_record_stride
|
||||
));
|
||||
}
|
||||
if let Some(directory) = region_collection_directory.as_ref() {
|
||||
notes.push(format!(
|
||||
"Region analysis now also exports the tagged live-entry directory rooted at metadata dword {}: {} entries chained from {:?} to {:?}.",
|
||||
directory.directory_root_dword_index,
|
||||
directory.entries.len(),
|
||||
directory.chain_head_live_entry_id,
|
||||
directory.chain_tail_live_entry_id
|
||||
));
|
||||
}
|
||||
if let Some(header) = report
|
||||
.save_placed_structure_collection_header_probe
|
||||
.as_ref()
|
||||
|
|
@ -3500,6 +3556,7 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
|||
world_economic_tuning,
|
||||
world_finance_neighborhood,
|
||||
region_collection_header: report.save_region_collection_header_probe.clone(),
|
||||
region_collection_directory,
|
||||
placed_structure_collection_header: report
|
||||
.save_placed_structure_collection_header_probe
|
||||
.clone(),
|
||||
|
|
@ -7506,6 +7563,10 @@ 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_directory_probe = parse_save_region_collection_directory_probe(
|
||||
bytes,
|
||||
save_region_collection_header_probe.as_ref(),
|
||||
);
|
||||
let save_placed_structure_collection_header_probe =
|
||||
parse_save_placed_structure_collection_header_probe(
|
||||
bytes,
|
||||
|
|
@ -7676,6 +7737,7 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> Sm
|
|||
save_company_collection_header_probe,
|
||||
save_chairman_profile_collection_header_probe,
|
||||
save_region_collection_header_probe,
|
||||
save_region_collection_directory_probe,
|
||||
save_placed_structure_collection_header_probe,
|
||||
save_company_roster_probe,
|
||||
save_chairman_profile_table_probe,
|
||||
|
|
@ -9645,6 +9707,86 @@ fn parse_save_region_collection_header_probe(
|
|||
)
|
||||
}
|
||||
|
||||
fn parse_save_region_collection_directory_probe(
|
||||
bytes: &[u8],
|
||||
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
||||
) -> Option<SmpSaveRegionCollectionDirectoryProbe> {
|
||||
let header_probe = header_probe?;
|
||||
if header_probe.source_kind != "save-region-tagged-header-counts" {
|
||||
return None;
|
||||
}
|
||||
let metadata_payload =
|
||||
bytes.get(header_probe.metadata_tag_offset + 4..header_probe.records_tag_offset)?;
|
||||
let directory_root_byte_offset =
|
||||
SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX.checked_mul(4)?;
|
||||
let live_record_count = header_probe.live_record_count as usize;
|
||||
let directory_len_dwords =
|
||||
live_record_count.checked_mul(SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT)?;
|
||||
let directory_len_bytes = directory_len_dwords.checked_mul(4)?;
|
||||
let directory_bytes = metadata_payload.get(
|
||||
directory_root_byte_offset..directory_root_byte_offset.checked_add(directory_len_bytes)?,
|
||||
)?;
|
||||
let mut entries = Vec::with_capacity(live_record_count);
|
||||
for index in 0..live_record_count {
|
||||
let entry_offset =
|
||||
index.checked_mul(SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT * 4)?;
|
||||
let payload_relative_offset = read_u32_at(directory_bytes, entry_offset)?;
|
||||
let previous_live_entry_id = read_u32_at(directory_bytes, entry_offset + 4)?;
|
||||
let next_live_entry_id = read_u32_at(directory_bytes, entry_offset + 8)?;
|
||||
entries.push(SmpSaveRegionCollectionDirectoryEntryProbe {
|
||||
live_entry_id: (index + 1) as u32,
|
||||
payload_relative_offset,
|
||||
payload_relative_offset_hex: format!("0x{payload_relative_offset:08x}"),
|
||||
payload_absolute_offset: header_probe
|
||||
.metadata_tag_offset
|
||||
.checked_add(4)?
|
||||
.checked_add(payload_relative_offset as usize)?,
|
||||
previous_live_entry_id,
|
||||
previous_live_entry_id_hex: format!("0x{previous_live_entry_id:08x}"),
|
||||
next_live_entry_id,
|
||||
next_live_entry_id_hex: format!("0x{next_live_entry_id:08x}"),
|
||||
});
|
||||
}
|
||||
let chain_head_live_entry_id = entries
|
||||
.iter()
|
||||
.find(|entry| entry.previous_live_entry_id == 0)
|
||||
.map(|entry| entry.live_entry_id);
|
||||
let chain_tail_live_entry_id = entries
|
||||
.iter()
|
||||
.find(|entry| entry.next_live_entry_id == 0)
|
||||
.map(|entry| entry.live_entry_id);
|
||||
let monotonic_offsets = entries
|
||||
.windows(2)
|
||||
.all(|window| window[0].payload_relative_offset < window[1].payload_relative_offset);
|
||||
Some(SmpSaveRegionCollectionDirectoryProbe {
|
||||
profile_family: header_probe.profile_family.clone(),
|
||||
source_kind: "save-region-live-directory".to_string(),
|
||||
semantic_family: "scenario-save-region-live-directory".to_string(),
|
||||
metadata_tag_offset: header_probe.metadata_tag_offset,
|
||||
records_tag_offset: header_probe.records_tag_offset,
|
||||
close_tag_offset: header_probe.close_tag_offset,
|
||||
directory_root_dword_index: SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX,
|
||||
directory_entry_dword_count: SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT,
|
||||
live_record_count: header_probe.live_record_count,
|
||||
live_id_bound: header_probe.live_id_bound,
|
||||
chain_head_live_entry_id,
|
||||
chain_tail_live_entry_id,
|
||||
entries,
|
||||
evidence: vec![
|
||||
"save-side region metadata payload exposes a live-entry directory immediately after the first 16 dwords, before the records tag".to_string(),
|
||||
format!(
|
||||
"region live directory decodes {} triplets of (payload_relative_offset, prev_live_entry_id, next_live_entry_id) from metadata dword index {}",
|
||||
header_probe.live_record_count,
|
||||
SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX
|
||||
),
|
||||
format!(
|
||||
"decoded directory preserves a head/tail chain {:?}->{:?} and monotonic payload offsets={monotonic_offsets}",
|
||||
chain_head_live_entry_id, chain_tail_live_entry_id
|
||||
),
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_save_placed_structure_collection_header_probe(
|
||||
bytes: &[u8],
|
||||
file_extension_hint: Option<&str>,
|
||||
|
|
@ -16541,6 +16683,44 @@ mod tests {
|
|||
],
|
||||
evidence: vec![],
|
||||
});
|
||||
report.save_region_collection_directory_probe =
|
||||
Some(SmpSaveRegionCollectionDirectoryProbe {
|
||||
profile_family: "rt3-105-save-container-v1".to_string(),
|
||||
source_kind: "save-region-live-directory".to_string(),
|
||||
semantic_family: "scenario-save-region-live-directory".to_string(),
|
||||
metadata_tag_offset: 0x3000,
|
||||
records_tag_offset: 0x3100,
|
||||
close_tag_offset: 0x3200,
|
||||
directory_root_dword_index: 16,
|
||||
directory_entry_dword_count: 3,
|
||||
live_record_count: 0x14,
|
||||
live_id_bound: 0x32,
|
||||
chain_head_live_entry_id: Some(1),
|
||||
chain_tail_live_entry_id: Some(20),
|
||||
entries: vec![
|
||||
SmpSaveRegionCollectionDirectoryEntryProbe {
|
||||
live_entry_id: 1,
|
||||
payload_relative_offset: 0x2af8,
|
||||
payload_relative_offset_hex: "0x00002af8".to_string(),
|
||||
payload_absolute_offset: 0x5afc,
|
||||
previous_live_entry_id: 0,
|
||||
previous_live_entry_id_hex: "0x00000000".to_string(),
|
||||
next_live_entry_id: 2,
|
||||
next_live_entry_id_hex: "0x00000002".to_string(),
|
||||
},
|
||||
SmpSaveRegionCollectionDirectoryEntryProbe {
|
||||
live_entry_id: 2,
|
||||
payload_relative_offset: 0x2ee0,
|
||||
payload_relative_offset_hex: "0x00002ee0".to_string(),
|
||||
payload_absolute_offset: 0x5ee4,
|
||||
previous_live_entry_id: 1,
|
||||
previous_live_entry_id_hex: "0x00000001".to_string(),
|
||||
next_live_entry_id: 0,
|
||||
next_live_entry_id_hex: "0x00000000".to_string(),
|
||||
},
|
||||
],
|
||||
evidence: vec![],
|
||||
});
|
||||
report.save_placed_structure_collection_header_probe =
|
||||
Some(SmpSaveTaggedCollectionHeaderProbe {
|
||||
profile_family: "rt3-105-save-container-v1".to_string(),
|
||||
|
|
@ -16640,6 +16820,9 @@ mod tests {
|
|||
.iter()
|
||||
.any(|note| note.contains("tagged region header reports live_record_count=20"))
|
||||
);
|
||||
assert!(slice.notes.iter().any(|note| {
|
||||
note.contains("tagged region metadata also exposes a live-entry directory")
|
||||
}));
|
||||
assert!(slice.notes.iter().any(|note| {
|
||||
note.contains("tagged placed-structure header reports live_record_count=2026")
|
||||
}));
|
||||
|
|
@ -16760,6 +16943,62 @@ mod tests {
|
|||
assert_eq!(probe.live_record_count, 0x14);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_region_collection_directory_probe_from_tagged_metadata_triplets() {
|
||||
let mut bytes = vec![0u8; 0x400];
|
||||
let metadata_tag_offset = 0x40usize;
|
||||
let records_tag_offset = 0x180usize;
|
||||
let close_tag_offset = 0x1c0usize;
|
||||
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, 0x03, 0x32, 0x03, 0x14, 1, 0, 0, 1, 1, 0x14, 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 triplets = [(0x2af8u32, 0u32, 2u32), (0x2ee0, 1, 3), (0x32c8, 2, 0)];
|
||||
for (index, (offset_word, prev, next)) in triplets.into_iter().enumerate() {
|
||||
let base = metadata_tag_offset
|
||||
+ 4
|
||||
+ (SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX + index * 3) * 4;
|
||||
bytes[base..base + 4].copy_from_slice(&offset_word.to_le_bytes());
|
||||
bytes[base + 4..base + 8].copy_from_slice(&prev.to_le_bytes());
|
||||
bytes[base + 8..base + 12].copy_from_slice(&next.to_le_bytes());
|
||||
}
|
||||
|
||||
let header_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");
|
||||
let directory_probe =
|
||||
parse_save_region_collection_directory_probe(&bytes, Some(&header_probe))
|
||||
.expect("region directory probe should parse");
|
||||
|
||||
assert_eq!(directory_probe.directory_root_dword_index, 16);
|
||||
assert_eq!(directory_probe.live_record_count, 3);
|
||||
assert_eq!(directory_probe.chain_head_live_entry_id, Some(1));
|
||||
assert_eq!(directory_probe.chain_tail_live_entry_id, Some(3));
|
||||
assert_eq!(directory_probe.entries.len(), 3);
|
||||
assert_eq!(directory_probe.entries[0].live_entry_id, 1);
|
||||
assert_eq!(directory_probe.entries[0].payload_relative_offset, 0x2af8);
|
||||
assert_eq!(directory_probe.entries[0].previous_live_entry_id, 0);
|
||||
assert_eq!(directory_probe.entries[0].next_live_entry_id, 2);
|
||||
assert_eq!(directory_probe.entries[2].live_entry_id, 3);
|
||||
assert_eq!(directory_probe.entries[2].previous_live_entry_id, 2);
|
||||
assert_eq!(directory_probe.entries[2].next_live_entry_id, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_placed_structure_tagged_collection_header_probe_from_exact_u32_tags() {
|
||||
let mut bytes = vec![0u8; 0x400];
|
||||
|
|
|
|||
|
|
@ -191,10 +191,11 @@ 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
|
||||
- save inspection now also exposes the tagged live region header `0x5209/0x520a/0x520b` plus its
|
||||
live-entry directory rooted at metadata dword `16`, 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
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@ Working rule:
|
|||
|
||||
## Next
|
||||
|
||||
- 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 region record body on top of the newly grounded tagged region
|
||||
header-plus-directory seam, 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.
|
||||
|
|
@ -43,17 +44,17 @@ Working rule:
|
|||
- 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.
|
||||
the record-body level, not the collection-identity level: the runtime now has a tagged region
|
||||
header-plus-directory seam and a tagged placed-structure header seam, 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.
|
||||
stride `0x1d5`) plus the live-entry directory rooted at metadata dword `16`, 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,
|
||||
ids, and region payload offsets 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.
|
||||
|
|
|
|||
|
|
@ -269,10 +269,11 @@ 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.
|
||||
Save inspection now also exposes the tagged live region header `0x5209/0x520a/0x520b` plus its
|
||||
live-entry directory rooted at metadata dword `16`, 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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue