diff --git a/crates/rrt-runtime/src/smp.rs b/crates/rrt-runtime/src/smp.rs index a29e157..d8494c4 100644 --- a/crates/rrt-runtime/src/smp.rs +++ b/crates/rrt-runtime/src/smp.rs @@ -3966,6 +3966,7 @@ fn build_infrastructure_asset_trace_report( "direct disassembly now also shows 0x00493be0 iterating live-entry ordinals through 0x00518380(ordinal, 0), converting each ordinal to a live id, then resolving that live id through 0x00518140 before handing the resulting payload pointer to 0x0048dcf0".to_string(), "direct disassembly now shows 0x00518680 loading the non-direct collection header, tombstone bitset, and live-id-bound-scaled 12-byte tables for the non-direct path before 0x00493be0 starts iterating".to_string(), "direct disassembly now also shows the shared child payload callback 0x00455fc0 opening 0x55f1, parsing three len-prefixed strings through 0x531380, opening 0x55f2, seeding the child through 0x455b70, dispatching slot +0x48, and then opening 0x55f3".to_string(), + "direct disassembly now also shows 0x00455b70 storing those three payload strings into [this+0x206/+0x20a/+0x20e], defaulting the second lane through a fixed literal when absent and defaulting the third lane back to the first string when absent".to_string(), format!( "current save-side probe reports {} embedded 0x55f1 rows with a third decoded string", side_buffer @@ -3980,7 +3981,6 @@ fn build_infrastructure_asset_trace_report( blockers: vec![ "how the payload streams reached through 0x00518380 -> 0x00518140 align with the embedded 0x55f1 name-pair groups and compact-prefix regimes surfaced by the save-side probe".to_string(), "which tagged values inside each payload stream correspond to the child count, optional primary-child ordinal, and the per-child shared tagged callback sequence consumed by 0x0048dcf0".to_string(), - "whether the third 0x55f1 string parsed by 0x00455fc0 is absent on grounded saves, stored under a different framing than the current probe, or only populated on a narrower infrastructure subset".to_string(), "which restored child fields or grouped rows retain the 0x38a5 embedded name-pair semantics before route/local-runtime follow-ons take over".to_string(), ], }, @@ -12546,6 +12546,26 @@ fn parse_save_len_prefixed_ascii_name(bytes: &[u8]) -> Option { Some(text.to_string()) } +fn parse_save_varlen_ascii_name_at(bytes: &[u8], offset: usize) -> Option<(String, usize)> { + let first = *bytes.get(offset)?; + if first == 0 { + return None; + } + let (len, header_len) = if first <= 0x7f { + (first as usize, 1usize) + } else { + let second = *bytes.get(offset + 1)? as usize; + ((((first as usize) & 0x7f) << 8) | second, 2usize) + }; + let start = offset + header_len; + let end = start.checked_add(len)?; + let text = std::str::from_utf8(bytes.get(start..end)?) + .ok()? + .trim_end_matches('\0') + .to_string(); + Some((text, end)) +} + fn parse_save_len_prefixed_ascii_name_pair(bytes: &[u8]) -> Option<(String, String)> { let (first, second, _) = parse_save_len_prefixed_ascii_name_triplet(bytes)?; Some((first, second)) @@ -12554,42 +12574,22 @@ fn parse_save_len_prefixed_ascii_name_pair(bytes: &[u8]) -> Option<(String, Stri fn parse_save_len_prefixed_ascii_name_triplet( bytes: &[u8], ) -> Option<(String, String, Option)> { - let first_len = *bytes.first()? as usize; - let first_end = 1 + first_len; - let first = std::str::from_utf8(bytes.get(1..first_end)?) - .ok()? - .trim_end_matches('\0') - .to_string(); + let (first, first_end) = parse_save_varlen_ascii_name_at(bytes, 0)?; let mut second_len_offset = first_end; while matches!(bytes.get(second_len_offset), Some(0)) { second_len_offset += 1; } - let second_len = *bytes.get(second_len_offset)? as usize; - let second_start = second_len_offset + 1; - let second = std::str::from_utf8(bytes.get(second_start..second_start + second_len)?) - .ok()? - .trim_end_matches('\0') - .to_string(); + let (second, second_end) = parse_save_varlen_ascii_name_at(bytes, second_len_offset)?; if first.is_empty() || second.is_empty() { return None; } - let mut third_len_offset = second_start + second_len; + let mut third_len_offset = second_end; while matches!(bytes.get(third_len_offset), Some(0)) { third_len_offset += 1; } - let third = bytes - .get(third_len_offset) - .copied() - .filter(|len| *len != 0) - .and_then(|third_len| { - let third_len = third_len as usize; - let third_start = third_len_offset + 1; - let text = std::str::from_utf8(bytes.get(third_start..third_start + third_len)?) - .ok()? - .trim_end_matches('\0') - .to_string(); - (!text.is_empty()).then_some(text) - }); + let third = parse_save_varlen_ascii_name_at(bytes, third_len_offset) + .map(|(text, _)| text) + .filter(|text| !text.is_empty()); Some((first, second, third)) } @@ -20409,6 +20409,18 @@ mod tests { assert_eq!(parsed.2.as_deref(), Some("Third")); } + #[test] + fn parses_save_len_prefixed_ascii_name_triplet_with_extended_length_prefix() { + let mut bytes = Vec::new(); + bytes.extend_from_slice(&[0x80, 0x03, b'A', b'B', b'C']); + bytes.extend_from_slice(&[0, 1, b'X']); + let parsed = parse_save_len_prefixed_ascii_name_triplet(&bytes) + .expect("triplet parser should decode extended-length prefix"); + assert_eq!(parsed.0, "ABC"); + assert_eq!(parsed.1, "X"); + assert_eq!(parsed.2, None); + } + #[test] fn aligns_placed_structure_dynamic_side_buffer_name_pairs_with_triplets() { let side_buffer = SmpSavePlacedStructureDynamicSideBufferProbe { diff --git a/docs/control-loop-atlas/runtime-roots-camera-and-support-families.md b/docs/control-loop-atlas/runtime-roots-camera-and-support-families.md index 45c58b6..5a9b63f 100644 --- a/docs/control-loop-atlas/runtime-roots-camera-and-support-families.md +++ b/docs/control-loop-atlas/runtime-roots-camera-and-support-families.md @@ -2937,9 +2937,12 @@ The low helper strip beneath that shared family is tighter now too: `0x0052ecd0` cached bridge bands, opens `0x55f1`, parses three len-prefixed strings through `0x00531380`, opens `0x55f2`, seeds the child through `0x00455b70`, dispatches slot `+0x48`, runs the local follow-on `0x0052ebd0`, and then opens `0x55f3`. The widened save-side probe currently still - sees only two embedded `0x55f1` strings on grounded `q.gms`, so the remaining payload question is - whether that third parsed string is absent on ordinary saves, hidden behind different framing, or - only populated on a narrower infrastructure subset. + sees only two embedded `0x55f1` strings on grounded `q.gms`, but `0x00455b70` now makes that + result much less mysterious: it stores the three payload strings into `[this+0x206/+0x20a/+0x20e]`, + defaulting the second lane through a fixed literal when absent and defaulting the third lane back + to the first string when absent. So the remaining payload question is no longer “where is the + third string hiding?”; it is how the current dual-name save-side rows align with the full + payload-stream grouping and the later tagged value roles. The child loader family is explicit now too: local `.rdata` at `0x005cfd00` proves the `Infrastructure` child vtable uses the shared tagged callback strip directly, with `+0x40 = 0x00455fc0`, `+0x48 = 0x00455870`, and `+0x4c = 0x00455930`. So the remaining diff --git a/docs/rehost-queue.md b/docs/rehost-queue.md index 9014cdb..d5c08d3 100644 --- a/docs/rehost-queue.md +++ b/docs/rehost-queue.md @@ -69,9 +69,12 @@ Working rule: disassembly now also shows the shared child payload callback `0x00455fc0` opening `0x55f1 -> 0x55f2 -> 0x55f3`, parsing three `0x55f1` strings through `0x00531380`, seeding the child through `0x00455b70`, and then dispatching slot `+0x48`; the widened save-side probe - currently sees `0` third `0x55f1` strings on grounded `q.gms`, so the next pass should ask - whether that third string is genuinely absent on ordinary saves or stored under different - framing than the current embedded-row scan. + currently sees `0` third `0x55f1` strings on grounded `q.gms`. That now looks less like a probe + failure and more like an ordinary fallback path, because direct disassembly of `0x00455b70` + stores the three payload strings into `[this+0x206/+0x20a/+0x20e]`, defaulting the second lane + through a fixed literal when absent and defaulting the third lane back to the first string when + absent. So the next pass should stay focused on payload-stream grouping and tagged value roles, + not on rediscovering a missing third-string encoding. - The child loader identity is closed now too: local `.rdata` at `0x005cfd00` proves the `Infrastructure` child vtable uses the shared tagged callback strip directly, with `+0x40 = 0x00455fc0`, `+0x48 = 0x00455870`, and `+0x4c = 0x00455930`. So the remaining