Decode save-side region policy rows
This commit is contained in:
parent
8861074c1b
commit
91651f3a9a
2 changed files with 105 additions and 12 deletions
|
|
@ -1648,7 +1648,7 @@ pub struct SmpSaveTrainCollectionDirectoryProbe {
|
||||||
pub evidence: Vec<String>,
|
pub evidence: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct SmpSaveRegionRecordTripletEntryProbe {
|
pub struct SmpSaveRegionRecordTripletEntryProbe {
|
||||||
pub record_index: usize,
|
pub record_index: usize,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
@ -1657,9 +1657,16 @@ pub struct SmpSaveRegionRecordTripletEntryProbe {
|
||||||
pub profile_tag_relative_offset: usize,
|
pub profile_tag_relative_offset: usize,
|
||||||
pub policy_chunk_len: usize,
|
pub policy_chunk_len: usize,
|
||||||
pub profile_chunk_len: usize,
|
pub profile_chunk_len: usize,
|
||||||
|
pub policy_leading_f32_0: f32,
|
||||||
|
pub policy_leading_f32_1: f32,
|
||||||
|
pub policy_leading_f32_2: f32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub policy_reserved_dwords: Vec<u32>,
|
||||||
|
pub policy_trailing_word: u16,
|
||||||
|
pub policy_trailing_word_hex: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct SmpSaveRegionRecordTripletProbe {
|
pub struct SmpSaveRegionRecordTripletProbe {
|
||||||
pub profile_family: String,
|
pub profile_family: String,
|
||||||
pub source_kind: String,
|
pub source_kind: String,
|
||||||
|
|
@ -3175,9 +3182,25 @@ pub fn load_save_slice_from_report(
|
||||||
}
|
}
|
||||||
if let Some(probe) = &report.save_region_record_triplet_probe {
|
if let Some(probe) = &report.save_region_record_triplet_probe {
|
||||||
notes.push(format!(
|
notes.push(format!(
|
||||||
"Raw save tagged region records also expose {} repeated 0x55f1/0x55f2/0x55f3 triplets in the records span; first name={:?}.",
|
"Raw save tagged region records also expose {} repeated 0x55f1/0x55f2/0x55f3 triplets in the records span; first name={:?}, first policy lanes=({:.3}, {:.3}, {:.3}), trailing_word={}.",
|
||||||
probe.record_count,
|
probe.record_count,
|
||||||
probe.entries.first().map(|entry| entry.name.as_str())
|
probe.entries.first().map(|entry| entry.name.as_str()),
|
||||||
|
probe.entries
|
||||||
|
.first()
|
||||||
|
.map(|entry| entry.policy_leading_f32_0)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
probe.entries
|
||||||
|
.first()
|
||||||
|
.map(|entry| entry.policy_leading_f32_1)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
probe.entries
|
||||||
|
.first()
|
||||||
|
.map(|entry| entry.policy_leading_f32_2)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
probe.entries
|
||||||
|
.first()
|
||||||
|
.map(|entry| entry.policy_trailing_word_hex.as_str())
|
||||||
|
.unwrap_or("0x0000")
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if let Some(probe) = &report.save_placed_structure_collection_header_probe {
|
if let Some(probe) = &report.save_placed_structure_collection_header_probe {
|
||||||
|
|
@ -3565,9 +3588,24 @@ pub fn inspect_save_company_and_chairman_analysis_bytes(
|
||||||
}
|
}
|
||||||
if let Some(triplets) = region_record_triplets.as_ref() {
|
if let Some(triplets) = region_record_triplets.as_ref() {
|
||||||
notes.push(format!(
|
notes.push(format!(
|
||||||
"Region analysis now also exports {} tagged 0x55f1/0x55f2/0x55f3 record triplets; first serialized region name={:?}.",
|
"Region analysis now also exports {} tagged 0x55f1/0x55f2/0x55f3 record triplets; first serialized region name={:?}, first policy lanes=({:.3}, {:.3}, {:.3}).",
|
||||||
triplets.record_count,
|
triplets.record_count,
|
||||||
triplets.entries.first().map(|entry| entry.name.as_str())
|
triplets.entries.first().map(|entry| entry.name.as_str()),
|
||||||
|
triplets
|
||||||
|
.entries
|
||||||
|
.first()
|
||||||
|
.map(|entry| entry.policy_leading_f32_0)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
triplets
|
||||||
|
.entries
|
||||||
|
.first()
|
||||||
|
.map(|entry| entry.policy_leading_f32_1)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
triplets
|
||||||
|
.entries
|
||||||
|
.first()
|
||||||
|
.map(|entry| entry.policy_leading_f32_2)
|
||||||
|
.unwrap_or_default()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if let Some(header) = report
|
if let Some(header) = report
|
||||||
|
|
@ -9939,6 +9977,19 @@ fn parse_save_region_record_triplet_probe(
|
||||||
let name = parse_save_len_prefixed_ascii_name(name_payload)?;
|
let name = parse_save_len_prefixed_ascii_name(name_payload)?;
|
||||||
let policy_chunk_len =
|
let policy_chunk_len =
|
||||||
profile_tag_relative_offset.checked_sub(policy_tag_relative_offset + 4)?;
|
profile_tag_relative_offset.checked_sub(policy_tag_relative_offset + 4)?;
|
||||||
|
if policy_chunk_len != 0x1a {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let policy_payload =
|
||||||
|
records_payload.get(policy_tag_relative_offset + 4..profile_tag_relative_offset)?;
|
||||||
|
let policy_leading_f32_0 = f32::from_bits(read_u32_at(policy_payload, 0)?);
|
||||||
|
let policy_leading_f32_1 = f32::from_bits(read_u32_at(policy_payload, 4)?);
|
||||||
|
let policy_leading_f32_2 = f32::from_bits(read_u32_at(policy_payload, 8)?);
|
||||||
|
let mut policy_reserved_dwords = Vec::with_capacity(3);
|
||||||
|
for dword_index in 0..3 {
|
||||||
|
policy_reserved_dwords.push(read_u32_at(policy_payload, 12 + dword_index * 4)?);
|
||||||
|
}
|
||||||
|
let policy_trailing_word = read_u16_at(policy_payload, 24)?;
|
||||||
let profile_chunk_len =
|
let profile_chunk_len =
|
||||||
next_record_relative_offset.checked_sub(profile_tag_relative_offset + 4)?;
|
next_record_relative_offset.checked_sub(profile_tag_relative_offset + 4)?;
|
||||||
entries.push(SmpSaveRegionRecordTripletEntryProbe {
|
entries.push(SmpSaveRegionRecordTripletEntryProbe {
|
||||||
|
|
@ -9949,6 +10000,12 @@ fn parse_save_region_record_triplet_probe(
|
||||||
profile_tag_relative_offset,
|
profile_tag_relative_offset,
|
||||||
policy_chunk_len,
|
policy_chunk_len,
|
||||||
profile_chunk_len,
|
profile_chunk_len,
|
||||||
|
policy_leading_f32_0,
|
||||||
|
policy_leading_f32_1,
|
||||||
|
policy_leading_f32_2,
|
||||||
|
policy_reserved_dwords,
|
||||||
|
policy_trailing_word,
|
||||||
|
policy_trailing_word_hex: format!("0x{policy_trailing_word:04x}"),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Some(SmpSaveRegionRecordTripletProbe {
|
Some(SmpSaveRegionRecordTripletProbe {
|
||||||
|
|
@ -9965,6 +10022,7 @@ fn parse_save_region_record_triplet_probe(
|
||||||
"decoded {} region record triplets with one len-prefixed name chunk, one fixed policy chunk, and one trailing profile payload chunk per record",
|
"decoded {} region record triplets with one len-prefixed name chunk, one fixed policy chunk, and one trailing profile payload chunk per record",
|
||||||
record_count
|
record_count
|
||||||
),
|
),
|
||||||
|
"each fixed 0x55f2 policy chunk currently decodes as three leading f32 lanes, three reserved dwords, and one trailing u16 word".to_string(),
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -16951,6 +17009,12 @@ mod tests {
|
||||||
profile_tag_relative_offset: 0x2e,
|
profile_tag_relative_offset: 0x2e,
|
||||||
policy_chunk_len: 0x1a,
|
policy_chunk_len: 0x1a,
|
||||||
profile_chunk_len: 0x40,
|
profile_chunk_len: 0x40,
|
||||||
|
policy_leading_f32_0: 368.0,
|
||||||
|
policy_leading_f32_1: 0.0,
|
||||||
|
policy_leading_f32_2: 92.0,
|
||||||
|
policy_reserved_dwords: vec![0, 0, 0],
|
||||||
|
policy_trailing_word: 1,
|
||||||
|
policy_trailing_word_hex: "0x0001".to_string(),
|
||||||
},
|
},
|
||||||
SmpSaveRegionRecordTripletEntryProbe {
|
SmpSaveRegionRecordTripletEntryProbe {
|
||||||
record_index: 1,
|
record_index: 1,
|
||||||
|
|
@ -16960,6 +17024,12 @@ mod tests {
|
||||||
profile_tag_relative_offset: 0x9c,
|
profile_tag_relative_offset: 0x9c,
|
||||||
policy_chunk_len: 0x1a,
|
policy_chunk_len: 0x1a,
|
||||||
profile_chunk_len: 0x20,
|
profile_chunk_len: 0x20,
|
||||||
|
policy_leading_f32_0: 552.0,
|
||||||
|
policy_leading_f32_1: 0.0,
|
||||||
|
policy_leading_f32_2: 276.0,
|
||||||
|
policy_reserved_dwords: vec![0, 0, 0],
|
||||||
|
policy_trailing_word: 1,
|
||||||
|
policy_trailing_word_hex: "0x0001".to_string(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
evidence: vec![],
|
evidence: vec![],
|
||||||
|
|
@ -17305,12 +17375,19 @@ mod tests {
|
||||||
.copy_from_slice(&0x0000520au32.to_le_bytes());
|
.copy_from_slice(&0x0000520au32.to_le_bytes());
|
||||||
bytes[close_tag_offset..close_tag_offset + 4].copy_from_slice(&0x0000520bu32.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;
|
let mut cursor = records_tag_offset + 4;
|
||||||
for name in ["Marker09", "Marker10"] {
|
for (name, x, density, y) in [
|
||||||
|
("Marker09", 368.0f32, 0.0f32, 92.0f32),
|
||||||
|
("Marker10", 552.0f32, 1.5f32, 276.0f32),
|
||||||
|
] {
|
||||||
bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_NAME_TAG.to_le_bytes());
|
bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_NAME_TAG.to_le_bytes());
|
||||||
bytes[cursor + 4] = name.len() as u8;
|
bytes[cursor + 4] = name.len() as u8;
|
||||||
bytes[cursor + 5..cursor + 5 + name.len()].copy_from_slice(name.as_bytes());
|
bytes[cursor + 5..cursor + 5 + name.len()].copy_from_slice(name.as_bytes());
|
||||||
cursor += 0x10;
|
cursor += 0x10;
|
||||||
bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_POLICY_TAG.to_le_bytes());
|
bytes[cursor..cursor + 2].copy_from_slice(&SAVE_REGION_RECORD_POLICY_TAG.to_le_bytes());
|
||||||
|
bytes[cursor + 4..cursor + 8].copy_from_slice(&x.to_bits().to_le_bytes());
|
||||||
|
bytes[cursor + 8..cursor + 12].copy_from_slice(&density.to_bits().to_le_bytes());
|
||||||
|
bytes[cursor + 12..cursor + 16].copy_from_slice(&y.to_bits().to_le_bytes());
|
||||||
|
bytes[cursor + 28..cursor + 30].copy_from_slice(&1u16.to_le_bytes());
|
||||||
cursor += 0x1e;
|
cursor += 0x1e;
|
||||||
bytes[cursor..cursor + 2]
|
bytes[cursor..cursor + 2]
|
||||||
.copy_from_slice(&SAVE_REGION_RECORD_PROFILE_TAG.to_le_bytes());
|
.copy_from_slice(&SAVE_REGION_RECORD_PROFILE_TAG.to_le_bytes());
|
||||||
|
|
@ -17343,7 +17420,18 @@ mod tests {
|
||||||
assert_eq!(triplet_probe.entries[0].name, "Marker09");
|
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].policy_tag_relative_offset, 0x10);
|
||||||
assert_eq!(triplet_probe.entries[0].profile_tag_relative_offset, 0x2e);
|
assert_eq!(triplet_probe.entries[0].profile_tag_relative_offset, 0x2e);
|
||||||
|
assert_eq!(triplet_probe.entries[0].policy_leading_f32_0, 368.0);
|
||||||
|
assert_eq!(triplet_probe.entries[0].policy_leading_f32_1, 0.0);
|
||||||
|
assert_eq!(triplet_probe.entries[0].policy_leading_f32_2, 92.0);
|
||||||
|
assert_eq!(
|
||||||
|
triplet_probe.entries[0].policy_reserved_dwords,
|
||||||
|
vec![0, 0, 0]
|
||||||
|
);
|
||||||
|
assert_eq!(triplet_probe.entries[0].policy_trailing_word, 1);
|
||||||
assert_eq!(triplet_probe.entries[1].name, "Marker10");
|
assert_eq!(triplet_probe.entries[1].name, "Marker10");
|
||||||
|
assert_eq!(triplet_probe.entries[1].policy_leading_f32_0, 552.0);
|
||||||
|
assert_eq!(triplet_probe.entries[1].policy_leading_f32_1, 1.5);
|
||||||
|
assert_eq!(triplet_probe.entries[1].policy_leading_f32_2, 276.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,12 @@ Working rule:
|
||||||
|
|
||||||
- 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) and its now
|
region seam (`0x5209/0x520a/0x520b`, stride hint `0x06`, `Marker09` record stems) and its now
|
||||||
grounded repeated `0x55f1/0x55f2/0x55f3` record-triplet envelope, especially the fixed `0x55f2`
|
grounded repeated `0x55f1/0x55f2/0x55f3` record-triplet envelope, especially the large `0x55f3`
|
||||||
policy row behind `[region+0x272/+0x25a/+0x25e]`, the pending bonus lane `[region+0x276]`,
|
profile payload that should carry the pending bonus lane `[region+0x276]`, completion latch
|
||||||
completion latch `[region+0x302]`, one-shot notice latch `[region+0x316]`, severity/source lane
|
`[region+0x302]`, one-shot notice latch `[region+0x316]`, severity/source lane `[region+0x25e]`,
|
||||||
`[region+0x25e]`, and any stable region-id or class discriminator that can drive shellless
|
and any stable region-id or class discriminator that can drive shellless city-connection
|
||||||
city-connection service.
|
service, now that the fixed `0x55f2` policy row already exposes its three leading f32 lanes,
|
||||||
|
reserved dwords, and trailing word structurally.
|
||||||
- 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.
|
||||||
|
|
@ -61,6 +62,10 @@ Working rule:
|
||||||
- That same corrected region seam now also exposes repeated `0x55f1/0x55f2/0x55f3` serialized
|
- 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
|
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.
|
city-connection pass can target the real record envelope instead of another blind scan.
|
||||||
|
- The fixed `0x55f2` row inside each region triplet is now decoded structurally as three leading
|
||||||
|
`f32` lanes, three reserved `u32` lanes, and a trailing `u16` word, so the next save-region
|
||||||
|
slice can focus on the larger `0x55f3` payload where the pending/completion/one-shot latches are
|
||||||
|
most likely to live.
|
||||||
- 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