Expose save-side region record triplets
This commit is contained in:
parent
86511f9670
commit
8861074c1b
2 changed files with 222 additions and 4 deletions
|
|
@ -152,6 +152,9 @@ const INDEXED_COLLECTION_SERIALIZED_HEADER_LEN: usize =
|
||||||
INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT * 4;
|
INDEXED_COLLECTION_SERIALIZED_HEADER_DWORD_COUNT * 4;
|
||||||
const SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX: usize = 16;
|
const SAVE_REGION_COLLECTION_DIRECTORY_ROOT_DWORD_INDEX: usize = 16;
|
||||||
const SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT: usize = 3;
|
const SAVE_REGION_COLLECTION_DIRECTORY_ENTRY_DWORD_COUNT: usize = 3;
|
||||||
|
const SAVE_REGION_RECORD_NAME_TAG: u16 = 0x55f1;
|
||||||
|
const SAVE_REGION_RECORD_POLICY_TAG: u16 = 0x55f2;
|
||||||
|
const SAVE_REGION_RECORD_PROFILE_TAG: u16 = 0x55f3;
|
||||||
const PACKED_EVENT_RECORDS_SYNTHETIC_MAGIC: &[u8; 8] = b"RPEVT001";
|
const PACKED_EVENT_RECORDS_SYNTHETIC_MAGIC: &[u8; 8] = b"RPEVT001";
|
||||||
const PACKED_EVENT_RECORD_SYNTHETIC_MAGIC: &[u8; 4] = b"RPE1";
|
const PACKED_EVENT_RECORD_SYNTHETIC_MAGIC: &[u8; 4] = b"RPE1";
|
||||||
const PACKED_EVENT_RECORD_TEMPLATE_SYNTHETIC_MAGIC: &[u8; 4] = b"RPT1";
|
const PACKED_EVENT_RECORD_TEMPLATE_SYNTHETIC_MAGIC: &[u8; 4] = b"RPT1";
|
||||||
|
|
@ -1645,6 +1648,30 @@ pub struct SmpSaveTrainCollectionDirectoryProbe {
|
||||||
pub evidence: Vec<String>,
|
pub evidence: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct SmpSaveRegionRecordTripletEntryProbe {
|
||||||
|
pub record_index: usize,
|
||||||
|
pub name: String,
|
||||||
|
pub name_tag_relative_offset: usize,
|
||||||
|
pub policy_tag_relative_offset: usize,
|
||||||
|
pub profile_tag_relative_offset: usize,
|
||||||
|
pub policy_chunk_len: usize,
|
||||||
|
pub profile_chunk_len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct SmpSaveRegionRecordTripletProbe {
|
||||||
|
pub profile_family: String,
|
||||||
|
pub source_kind: String,
|
||||||
|
pub semantic_family: String,
|
||||||
|
pub records_tag_offset: usize,
|
||||||
|
pub close_tag_offset: usize,
|
||||||
|
pub record_count: usize,
|
||||||
|
#[serde(default)]
|
||||||
|
pub entries: Vec<SmpSaveRegionRecordTripletEntryProbe>,
|
||||||
|
pub evidence: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct SmpRt3105SaveNameTableProbe {
|
pub struct SmpRt3105SaveNameTableProbe {
|
||||||
pub profile_family: String,
|
pub profile_family: String,
|
||||||
|
|
@ -2585,6 +2612,8 @@ pub struct SmpSaveCompanyChairmanAnalysisReport {
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub region_collection_header: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
pub region_collection_header: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
pub region_record_triplets: Option<SmpSaveRegionRecordTripletProbe>,
|
||||||
|
#[serde(default)]
|
||||||
pub placed_structure_collection_header: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
pub placed_structure_collection_header: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub company_entries: Vec<SmpSaveCompanyRecordAnalysisEntry>,
|
pub company_entries: Vec<SmpSaveCompanyRecordAnalysisEntry>,
|
||||||
|
|
@ -2848,6 +2877,7 @@ pub struct SmpInspectionReport {
|
||||||
pub save_train_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
pub save_train_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
||||||
pub save_train_collection_directory_probe: Option<SmpSaveTrainCollectionDirectoryProbe>,
|
pub save_train_collection_directory_probe: Option<SmpSaveTrainCollectionDirectoryProbe>,
|
||||||
pub save_region_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
pub save_region_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
||||||
|
pub save_region_record_triplet_probe: Option<SmpSaveRegionRecordTripletProbe>,
|
||||||
pub save_placed_structure_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
pub save_placed_structure_collection_header_probe: Option<SmpSaveTaggedCollectionHeaderProbe>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub save_company_roster_probe: Option<SmpLoadedCompanyRoster>,
|
pub save_company_roster_probe: Option<SmpLoadedCompanyRoster>,
|
||||||
|
|
@ -3143,6 +3173,13 @@ pub fn load_save_slice_from_report(
|
||||||
probe.close_tag_offset
|
probe.close_tag_offset
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
if let Some(probe) = &report.save_region_record_triplet_probe {
|
||||||
|
notes.push(format!(
|
||||||
|
"Raw save tagged region records also expose {} repeated 0x55f1/0x55f2/0x55f3 triplets in the records span; first name={:?}.",
|
||||||
|
probe.record_count,
|
||||||
|
probe.entries.first().map(|entry| entry.name.as_str())
|
||||||
|
));
|
||||||
|
}
|
||||||
if let Some(probe) = &report.save_placed_structure_collection_header_probe {
|
if let Some(probe) = &report.save_placed_structure_collection_header_probe {
|
||||||
notes.push(format!(
|
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}.",
|
"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}.",
|
||||||
|
|
@ -3215,6 +3252,7 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
||||||
let world_economic_tuning = report.save_world_economic_tuning_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 world_finance_neighborhood = report.save_world_finance_neighborhood_probe.clone();
|
||||||
let train_collection_directory = report.save_train_collection_directory_probe.clone();
|
let train_collection_directory = report.save_train_collection_directory_probe.clone();
|
||||||
|
let region_record_triplets = report.save_region_record_triplet_probe.clone();
|
||||||
let company_header_probe = report.save_company_collection_header_probe.as_ref();
|
let company_header_probe = report.save_company_collection_header_probe.as_ref();
|
||||||
let chairman_header_probe = report
|
let chairman_header_probe = report
|
||||||
.save_chairman_profile_collection_header_probe
|
.save_chairman_profile_collection_header_probe
|
||||||
|
|
@ -3525,6 +3563,13 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
||||||
header.live_record_count, header.live_id_bound, header.direct_record_stride
|
header.live_record_count, header.live_id_bound, header.direct_record_stride
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
if let Some(triplets) = region_record_triplets.as_ref() {
|
||||||
|
notes.push(format!(
|
||||||
|
"Region analysis now also exports {} tagged 0x55f1/0x55f2/0x55f3 record triplets; first serialized region name={:?}.",
|
||||||
|
triplets.record_count,
|
||||||
|
triplets.entries.first().map(|entry| entry.name.as_str())
|
||||||
|
));
|
||||||
|
}
|
||||||
if let Some(header) = report
|
if let Some(header) = report
|
||||||
.save_placed_structure_collection_header_probe
|
.save_placed_structure_collection_header_probe
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
|
@ -3578,6 +3623,7 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
||||||
train_collection_header: report.save_train_collection_header_probe.clone(),
|
train_collection_header: report.save_train_collection_header_probe.clone(),
|
||||||
train_collection_directory,
|
train_collection_directory,
|
||||||
region_collection_header: report.save_region_collection_header_probe.clone(),
|
region_collection_header: report.save_region_collection_header_probe.clone(),
|
||||||
|
region_record_triplets,
|
||||||
placed_structure_collection_header: report
|
placed_structure_collection_header: report
|
||||||
.save_placed_structure_collection_header_probe
|
.save_placed_structure_collection_header_probe
|
||||||
.clone(),
|
.clone(),
|
||||||
|
|
@ -7593,6 +7639,8 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> Sm
|
||||||
file_extension_hint.as_deref(),
|
file_extension_hint.as_deref(),
|
||||||
container_profile.as_ref(),
|
container_profile.as_ref(),
|
||||||
);
|
);
|
||||||
|
let save_region_record_triplet_probe =
|
||||||
|
parse_save_region_record_triplet_probe(bytes, save_region_collection_header_probe.as_ref());
|
||||||
let save_placed_structure_collection_header_probe =
|
let save_placed_structure_collection_header_probe =
|
||||||
parse_save_placed_structure_collection_header_probe(
|
parse_save_placed_structure_collection_header_probe(
|
||||||
bytes,
|
bytes,
|
||||||
|
|
@ -7765,6 +7813,7 @@ fn inspect_bundle_bytes(bytes: &[u8], file_extension_hint: Option<String>) -> Sm
|
||||||
save_train_collection_header_probe,
|
save_train_collection_header_probe,
|
||||||
save_train_collection_directory_probe,
|
save_train_collection_directory_probe,
|
||||||
save_region_collection_header_probe,
|
save_region_collection_header_probe,
|
||||||
|
save_region_record_triplet_probe,
|
||||||
save_placed_structure_collection_header_probe,
|
save_placed_structure_collection_header_probe,
|
||||||
save_company_roster_probe,
|
save_company_roster_probe,
|
||||||
save_chairman_profile_table_probe,
|
save_chairman_profile_table_probe,
|
||||||
|
|
@ -9850,6 +9899,76 @@ fn parse_save_region_collection_header_probe(
|
||||||
.then_some(probe)
|
.then_some(probe)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_save_region_record_triplet_probe(
|
||||||
|
bytes: &[u8],
|
||||||
|
header_probe: Option<&SmpSaveTaggedCollectionHeaderProbe>,
|
||||||
|
) -> Option<SmpSaveRegionRecordTripletProbe> {
|
||||||
|
let header_probe = header_probe?;
|
||||||
|
if header_probe.source_kind != "save-region-tagged-header-counts" {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let records_payload =
|
||||||
|
bytes.get(header_probe.records_tag_offset + 4..header_probe.close_tag_offset)?;
|
||||||
|
let name_offsets = find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_NAME_TAG);
|
||||||
|
let policy_offsets = find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_POLICY_TAG);
|
||||||
|
let profile_offsets = find_u16_le_offsets(records_payload, SAVE_REGION_RECORD_PROFILE_TAG);
|
||||||
|
let record_count = header_probe.live_record_count as usize;
|
||||||
|
if name_offsets.len() != record_count
|
||||||
|
|| policy_offsets.len() != record_count
|
||||||
|
|| profile_offsets.len() != record_count
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut entries = Vec::with_capacity(record_count);
|
||||||
|
for index in 0..record_count {
|
||||||
|
let name_tag_relative_offset = name_offsets[index];
|
||||||
|
let policy_tag_relative_offset = policy_offsets[index];
|
||||||
|
let profile_tag_relative_offset = profile_offsets[index];
|
||||||
|
let next_record_relative_offset = name_offsets
|
||||||
|
.get(index + 1)
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(records_payload.len());
|
||||||
|
if !(name_tag_relative_offset < policy_tag_relative_offset
|
||||||
|
&& policy_tag_relative_offset < profile_tag_relative_offset
|
||||||
|
&& profile_tag_relative_offset < next_record_relative_offset)
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let name_payload =
|
||||||
|
records_payload.get(name_tag_relative_offset + 4..policy_tag_relative_offset)?;
|
||||||
|
let name = parse_save_len_prefixed_ascii_name(name_payload)?;
|
||||||
|
let policy_chunk_len =
|
||||||
|
profile_tag_relative_offset.checked_sub(policy_tag_relative_offset + 4)?;
|
||||||
|
let profile_chunk_len =
|
||||||
|
next_record_relative_offset.checked_sub(profile_tag_relative_offset + 4)?;
|
||||||
|
entries.push(SmpSaveRegionRecordTripletEntryProbe {
|
||||||
|
record_index: index,
|
||||||
|
name,
|
||||||
|
name_tag_relative_offset,
|
||||||
|
policy_tag_relative_offset,
|
||||||
|
profile_tag_relative_offset,
|
||||||
|
policy_chunk_len,
|
||||||
|
profile_chunk_len,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Some(SmpSaveRegionRecordTripletProbe {
|
||||||
|
profile_family: header_probe.profile_family.clone(),
|
||||||
|
source_kind: "save-region-record-triplets".to_string(),
|
||||||
|
semantic_family: "scenario-save-region-record-triplets".to_string(),
|
||||||
|
records_tag_offset: header_probe.records_tag_offset,
|
||||||
|
close_tag_offset: header_probe.close_tag_offset,
|
||||||
|
record_count,
|
||||||
|
entries,
|
||||||
|
evidence: vec![
|
||||||
|
"save-side region records in the non-direct Marker09 family are serialized as repeated 0x55f1/0x55f2/0x55f3 triplets inside the records span".to_string(),
|
||||||
|
format!(
|
||||||
|
"decoded {} region record triplets with one len-prefixed name chunk, one fixed policy chunk, and one trailing profile payload chunk per record",
|
||||||
|
record_count
|
||||||
|
),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_save_placed_structure_collection_header_probe(
|
fn parse_save_placed_structure_collection_header_probe(
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
file_extension_hint: Option<&str>,
|
file_extension_hint: Option<&str>,
|
||||||
|
|
@ -9993,6 +10112,13 @@ fn parse_save_tagged_collection_header_probe(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_save_len_prefixed_ascii_name(bytes: &[u8]) -> Option<String> {
|
||||||
|
let len = *bytes.first()? as usize;
|
||||||
|
let text_bytes = bytes.get(1..1 + len)?;
|
||||||
|
let text = std::str::from_utf8(text_bytes).ok()?.trim_end_matches('\0');
|
||||||
|
Some(text.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_rt3_105_save_name_table_probe(
|
fn parse_rt3_105_save_name_table_probe(
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
file_extension_hint: Option<&str>,
|
file_extension_hint: Option<&str>,
|
||||||
|
|
@ -16809,6 +16935,35 @@ mod tests {
|
||||||
],
|
],
|
||||||
evidence: vec![],
|
evidence: vec![],
|
||||||
});
|
});
|
||||||
|
report.save_region_record_triplet_probe = Some(SmpSaveRegionRecordTripletProbe {
|
||||||
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
||||||
|
source_kind: "save-region-record-triplets".to_string(),
|
||||||
|
semantic_family: "scenario-save-region-record-triplets".to_string(),
|
||||||
|
records_tag_offset: 0x5100,
|
||||||
|
close_tag_offset: 0x5200,
|
||||||
|
record_count: 2,
|
||||||
|
entries: vec![
|
||||||
|
SmpSaveRegionRecordTripletEntryProbe {
|
||||||
|
record_index: 0,
|
||||||
|
name: "Marker09".to_string(),
|
||||||
|
name_tag_relative_offset: 0,
|
||||||
|
policy_tag_relative_offset: 0x10,
|
||||||
|
profile_tag_relative_offset: 0x2e,
|
||||||
|
policy_chunk_len: 0x1a,
|
||||||
|
profile_chunk_len: 0x40,
|
||||||
|
},
|
||||||
|
SmpSaveRegionRecordTripletEntryProbe {
|
||||||
|
record_index: 1,
|
||||||
|
name: "Marker10".to_string(),
|
||||||
|
name_tag_relative_offset: 0x6e,
|
||||||
|
policy_tag_relative_offset: 0x7e,
|
||||||
|
profile_tag_relative_offset: 0x9c,
|
||||||
|
policy_chunk_len: 0x1a,
|
||||||
|
profile_chunk_len: 0x20,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
evidence: vec![],
|
||||||
|
});
|
||||||
report.save_placed_structure_collection_header_probe =
|
report.save_placed_structure_collection_header_probe =
|
||||||
Some(SmpSaveTaggedCollectionHeaderProbe {
|
Some(SmpSaveTaggedCollectionHeaderProbe {
|
||||||
profile_family: "rt3-105-save-container-v1".to_string(),
|
profile_family: "rt3-105-save-container-v1".to_string(),
|
||||||
|
|
@ -16916,6 +17071,11 @@ mod tests {
|
||||||
note.contains("tagged region header reports live_record_count=145")
|
note.contains("tagged region header reports live_record_count=145")
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
assert!(slice.notes.iter().any(|note| {
|
||||||
|
note.contains(
|
||||||
|
"tagged region records also expose 2 repeated 0x55f1/0x55f2/0x55f3 triplets",
|
||||||
|
)
|
||||||
|
}));
|
||||||
assert!(slice.notes.iter().any(|note| {
|
assert!(slice.notes.iter().any(|note| {
|
||||||
note.contains("tagged placed-structure header reports live_record_count=2026")
|
note.contains("tagged placed-structure header reports live_record_count=2026")
|
||||||
}));
|
}));
|
||||||
|
|
@ -17133,6 +17293,59 @@ mod tests {
|
||||||
assert_eq!(directory_probe.entries[2].next_live_entry_id, 0);
|
assert_eq!(directory_probe.entries[2].next_live_entry_id, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_region_record_triplet_probe_from_marker09_records() {
|
||||||
|
let mut bytes = vec![0u8; 0x400];
|
||||||
|
let metadata_tag_offset = 0x40usize;
|
||||||
|
let records_tag_offset = 0x140usize;
|
||||||
|
let close_tag_offset = 0x260usize;
|
||||||
|
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 mut cursor = records_tag_offset + 4;
|
||||||
|
for name in ["Marker09", "Marker10"] {
|
||||||
|
bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_NAME_TAG.to_le_bytes());
|
||||||
|
bytes[cursor + 4] = name.len() as u8;
|
||||||
|
bytes[cursor + 5..cursor + 5 + name.len()].copy_from_slice(name.as_bytes());
|
||||||
|
cursor += 0x10;
|
||||||
|
bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_POLICY_TAG.to_le_bytes());
|
||||||
|
cursor += 0x1e;
|
||||||
|
bytes[cursor..cursor + 2]
|
||||||
|
.copy_from_slice(&SAVE_REGION_RECORD_PROFILE_TAG.to_le_bytes());
|
||||||
|
cursor += 0x40;
|
||||||
|
}
|
||||||
|
|
||||||
|
let header_probe = 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,
|
||||||
|
records_tag_offset,
|
||||||
|
close_tag_offset,
|
||||||
|
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: 0x96,
|
||||||
|
live_id_bound_hex: "0x00000096".to_string(),
|
||||||
|
live_record_count: 2,
|
||||||
|
live_record_count_hex: "0x00000002".to_string(),
|
||||||
|
header_words: vec![0, 6, 0x0a, 0x14, 0x96, 0x02],
|
||||||
|
header_hex_words: vec![],
|
||||||
|
evidence: vec![],
|
||||||
|
};
|
||||||
|
let triplet_probe = parse_save_region_record_triplet_probe(&bytes, Some(&header_probe))
|
||||||
|
.expect("region triplet probe should parse");
|
||||||
|
|
||||||
|
assert_eq!(triplet_probe.record_count, 2);
|
||||||
|
assert_eq!(triplet_probe.entries[0].name, "Marker09");
|
||||||
|
assert_eq!(triplet_probe.entries[0].policy_tag_relative_offset, 0x10);
|
||||||
|
assert_eq!(triplet_probe.entries[0].profile_tag_relative_offset, 0x2e);
|
||||||
|
assert_eq!(triplet_probe.entries[1].name, "Marker10");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_placed_structure_tagged_collection_header_probe_from_exact_u32_tags() {
|
fn parses_placed_structure_tagged_collection_header_probe_from_exact_u32_tags() {
|
||||||
let mut bytes = vec![0u8; 0x400];
|
let mut bytes = vec![0u8; 0x400];
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,12 @@ Working rule:
|
||||||
## Next
|
## Next
|
||||||
|
|
||||||
- Reconstruct the save-side region record body on top of the newly corrected non-direct tagged
|
- Reconstruct the save-side region record body on top of the newly corrected non-direct tagged
|
||||||
region seam (`0x5209/0x520a/0x520b`, stride hint `0x06`, `Marker09` record stems), especially
|
region seam (`0x5209/0x520a/0x520b`, stride hint `0x06`, `Marker09` record stems) and its now
|
||||||
the pending bonus lane `[region+0x276]`, completion latch `[region+0x302]`, one-shot notice
|
grounded repeated `0x55f1/0x55f2/0x55f3` record-triplet envelope, especially the fixed `0x55f2`
|
||||||
latch `[region+0x316]`, severity/source lane `[region+0x25e]`, and any stable region-id or
|
policy row behind `[region+0x272/+0x25a/+0x25e]`, the pending bonus lane `[region+0x276]`,
|
||||||
class discriminator that can drive shellless city-connection service.
|
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
|
- 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
|
`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.
|
stop depending on atlas-only placed-structure and local-runtime refresh notes.
|
||||||
|
|
@ -56,6 +58,9 @@ Working rule:
|
||||||
at metadata dword `16`, while the actual region family is the larger non-direct `Marker09`
|
at metadata dword `16`, while the actual region family is the larger non-direct `Marker09`
|
||||||
collection with live_id/count `0x96/0x91`; the tagged placed-structure header
|
collection with live_id/count `0x96/0x91`; the tagged placed-structure header
|
||||||
(`0x36b1/0x36b2/0x36b3`) remains grounded alongside them.
|
(`0x36b1/0x36b2/0x36b3`) remains grounded alongside them.
|
||||||
|
- That same corrected region seam now also exposes repeated `0x55f1/0x55f2/0x55f3` serialized
|
||||||
|
record triplets with len-prefixed names plus fixed policy/profile chunk lengths, so the next
|
||||||
|
city-connection pass can target the real record envelope instead of another blind scan.
|
||||||
- Stepped calendar progression now also refreshes save-world owner time fields, including packed
|
- 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.
|
year, packed tuple words, absolute counter, and the derived selected-year gap scalar.
|
||||||
- Automatic year-rollover calendar stepping now invokes periodic-boundary service.
|
- Automatic year-rollover calendar stepping now invokes periodic-boundary service.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue