nmarshall commited on
Commit
1a0b6ef
·
1 Parent(s): 586efda

gameplay: port recovered byte formatter

Browse files
crates/urt-gameplay/src/bin/urt-lifecycle.rs CHANGED
@@ -394,6 +394,63 @@ fn run() -> Result<(), String> {
394
  second.cached_ping as u32
395
  );
396
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  "scoreboard-stream" if w.len() >= 6 && w[4] <= 64 && w[5] <= 64 => {
398
  let n = w[4] as usize;
399
  let count = w[5] as usize;
 
394
  second.cached_ping as u32
395
  );
396
  }
397
+ "qvm-format" if w.len() >= 2 && w[0] <= 4096 && w[1] <= 64 => {
398
+ let len = w[0] as usize;
399
+ let n = w[1] as usize;
400
+ if w.len() < 2 + len {
401
+ return Err("missing format".into());
402
+ }
403
+ let bytes = |v: &[u32]| -> Result<Vec<u8>, String> {
404
+ v.iter()
405
+ .map(|v| u8::try_from(*v).map_err(|_| "invalid byte".into()))
406
+ .collect()
407
+ };
408
+ let template = bytes(&w[2..2 + len])?;
409
+ let mut pos = 2 + len;
410
+ let mut storage = vec![];
411
+ let mut tags = vec![];
412
+ for _ in 0..n {
413
+ let tag = *w.get(pos).ok_or("missing argument")?;
414
+ pos += 1;
415
+ match tag {
416
+ 0 => {
417
+ let v = *w.get(pos).ok_or("missing word")?;
418
+ pos += 1;
419
+ tags.push((0, v, 0));
420
+ }
421
+ 1 => {
422
+ let len = *w.get(pos).ok_or("missing byte length")? as usize;
423
+ pos += 1;
424
+ if len > 32768 || w.len() - pos < len {
425
+ return Err("invalid argument length".into());
426
+ }
427
+ storage.push(bytes(&w[pos..pos + len])?);
428
+ pos += len;
429
+ tags.push((1, 0, storage.len() - 1));
430
+ }
431
+ 2 => tags.push((2, 0, 0)),
432
+ _ => return Err("invalid argument type".into()),
433
+ }
434
+ }
435
+ if pos != w.len() {
436
+ return Err("trailing format words".into());
437
+ }
438
+ let args: Vec<_> = tags
439
+ .iter()
440
+ .map(|(t, v, i)| match t {
441
+ 0 => urt_gameplay::qvm_format::Argument::Word(*v as i32),
442
+ 1 => urt_gameplay::qvm_format::Argument::Bytes(Some(&storage[*i])),
443
+ _ => urt_gameplay::qvm_format::Argument::Bytes(None),
444
+ })
445
+ .collect();
446
+ let out = urt_gameplay::qvm_format::render(&template, &args)
447
+ .map_err(|e| format!("unsupported input: {e:?}"))?;
448
+ println!(
449
+ "{{\"cstring_hex\":\"{}\",\"count\":{}}}",
450
+ out.iter().map(|v| format!("{v:02x}")).collect::<String>(),
451
+ out.len() - 1
452
+ );
453
+ }
454
  "scoreboard-stream" if w.len() >= 6 && w[4] <= 64 && w[5] <= 64 => {
455
  let n = w[4] as usize;
456
  let count = w[5] as usize;
crates/urt-gameplay/src/lib.rs CHANGED
@@ -165,3 +165,5 @@ pub mod ranking_population;
165
  pub mod scoreboard;
166
 
167
  pub mod scoreboard_wire;
 
 
 
165
  pub mod scoreboard;
166
 
167
  pub mod scoreboard_wire;
168
+
169
+ pub mod qvm_format;
crates/urt-gameplay/src/qvm_format.rs ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Recovered37914 integer/string/character subset. This is not host printf.
2
+ #[derive(Clone, Copy, Debug)]
3
+ pub enum Argument<'a> {
4
+ Word(i32),
5
+ Bytes(Option<&'a [u8]>),
6
+ }
7
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
8
+ pub enum Error {
9
+ Malformed,
10
+ Argument,
11
+ Limit,
12
+ PaddingDomain,
13
+ UnsupportedFloat,
14
+ }
15
+ /// Preserve original wrapping-negation/remainder behavior, including INT_MIN.
16
+ pub fn decimal(value: i32) -> Vec<u8> {
17
+ let mut x = if value < 0 {
18
+ value.wrapping_neg()
19
+ } else {
20
+ value
21
+ };
22
+ let mut digits = vec![];
23
+ loop {
24
+ digits.push((48 + x % 10) as u8);
25
+ x /= 10;
26
+ if x == 0 {
27
+ break;
28
+ }
29
+ }
30
+ if value < 0 {
31
+ digits.push(b'-');
32
+ }
33
+ digits.reverse();
34
+ digits
35
+ }
36
+ fn append(out: &mut Vec<u8>, data: &[u8]) -> Result<(), Error> {
37
+ if out.len() + data.len() > 32768 {
38
+ return Err(Error::Limit);
39
+ }
40
+ out.extend_from_slice(data);
41
+ Ok(())
42
+ }
43
+ /// Flags/width/precision follow source dispatch. Strings always trail-pad;
44
+ /// integer precision is ignored; unknown conversions emit a word's low byte.
45
+ pub fn render(template: &[u8], arguments: &[Argument<'_>]) -> Result<Vec<u8>, Error> {
46
+ let end = template
47
+ .iter()
48
+ .position(|b| *b == 0)
49
+ .unwrap_or(template.len());
50
+ let t = &template[..end];
51
+ let mut out = vec![];
52
+ let mut i = 0;
53
+ let mut arg = 0;
54
+ while i < t.len() {
55
+ if t[i] != b'%' {
56
+ append(&mut out, &t[i..i + 1])?;
57
+ i += 1;
58
+ continue;
59
+ }
60
+ i += 1;
61
+ let mut left = false;
62
+ let mut zero = false;
63
+ while i < t.len() && [b'-', b'0'].contains(&t[i]) {
64
+ left |= t[i] == b'-';
65
+ zero |= t[i] == b'0';
66
+ i += 1;
67
+ }
68
+ let mut width = 0usize;
69
+ while i < t.len() && t[i].is_ascii_digit() {
70
+ width = width
71
+ .checked_mul(10)
72
+ .and_then(|v| v.checked_add((t[i] - b'0') as usize))
73
+ .ok_or(Error::Limit)?;
74
+ if width > 4096 {
75
+ return Err(Error::Limit);
76
+ }
77
+ i += 1;
78
+ }
79
+ let mut precision = None;
80
+ if i < t.len() && t[i] == b'.' {
81
+ i += 1;
82
+ let mut n = 0usize;
83
+ while i < t.len() && t[i].is_ascii_digit() {
84
+ n = n
85
+ .checked_mul(10)
86
+ .and_then(|v| v.checked_add((t[i] - b'0') as usize))
87
+ .ok_or(Error::Limit)?;
88
+ if n > 4096 {
89
+ return Err(Error::Limit);
90
+ }
91
+ i += 1;
92
+ }
93
+ precision = Some(n);
94
+ }
95
+ let spec = *t.get(i).ok_or(Error::Malformed)?;
96
+ i += 1;
97
+ if spec == b'%' {
98
+ append(&mut out, b"%")?;
99
+ continue;
100
+ }
101
+ if spec == b'f' {
102
+ return Err(Error::UnsupportedFloat);
103
+ }
104
+ let value = arguments.get(arg).ok_or(Error::Argument)?;
105
+ arg += 1;
106
+ if [b'd', b'i', b'p'].contains(&spec) {
107
+ let Argument::Word(value) = value else {
108
+ return Err(Error::Argument);
109
+ };
110
+ let digits = decimal(*value);
111
+ if left && width < digits.len() {
112
+ return Err(Error::PaddingDomain);
113
+ }
114
+ let padding = vec![if zero { b'0' } else { b' ' }; width.saturating_sub(digits.len())];
115
+ if !left {
116
+ append(&mut out, &padding)?;
117
+ }
118
+ append(&mut out, &digits)?;
119
+ if left {
120
+ append(&mut out, &padding)?;
121
+ }
122
+ } else if spec == b's' {
123
+ let Argument::Bytes(value) = value else {
124
+ return Err(Error::Argument);
125
+ };
126
+ let (bytes, precision) = match value {
127
+ None => (b"(null)".as_slice(), None),
128
+ Some(v) => (*v, precision),
129
+ };
130
+ let length = bytes.iter().position(|b| *b == 0).unwrap_or(bytes.len());
131
+ let length = precision.map_or(length, |n| n.min(length));
132
+ append(&mut out, &bytes[..length])?;
133
+ append(&mut out, &vec![b' '; width.saturating_sub(length)])?;
134
+ } else {
135
+ let Argument::Word(value) = value else {
136
+ return Err(Error::Argument);
137
+ };
138
+ append(&mut out, &[*value as u8])?;
139
+ }
140
+ }
141
+ out.push(0);
142
+ Ok(out)
143
+ }
144
+ #[cfg(test)]
145
+ mod tests {
146
+ use super::*;
147
+ #[test]
148
+ fn source_padding_precision_and_unknown_dispatch() {
149
+ assert_eq!(
150
+ render(
151
+ b"%05i/%8.2s/%q",
152
+ &[
153
+ Argument::Word(-12),
154
+ Argument::Bytes(Some(b"ABCDE")),
155
+ Argument::Word(321)
156
+ ]
157
+ )
158
+ .unwrap(),
159
+ b"00-12/AB /A\0"
160
+ );
161
+ assert_eq!(
162
+ render(b"%.0s", &[Argument::Bytes(None)]).unwrap(),
163
+ b"(null)\0"
164
+ );
165
+ }
166
+ #[test]
167
+ fn wrapping_minimum_and_embedded_character_nul() {
168
+ assert_ne!(decimal(i32::MIN), b"-2147483648");
169
+ assert_eq!(render(b"%cX", &[Argument::Word(0)]).unwrap(), [0, 88, 0]);
170
+ assert_eq!(decimal(-1), b"-1");
171
+ }
172
+ #[test]
173
+ fn unsupported_or_unsafe_domains_reject() {
174
+ assert_eq!(
175
+ render(b"%-1i", &[Argument::Word(123)]),
176
+ Err(Error::PaddingDomain)
177
+ );
178
+ assert_eq!(render(b"%", &[]), Err(Error::Malformed));
179
+ assert_eq!(render(b"%f", &[]), Err(Error::UnsupportedFloat));
180
+ assert_eq!(render(b"%s", &[Argument::Word(0)]), Err(Error::Argument));
181
+ assert_eq!(render(b"%99999i", &[Argument::Word(1)]), Err(Error::Limit));
182
+ }
183
+ }
docs/FINDINGS.md CHANGED
@@ -1448,3 +1448,6 @@ RE-802–811 adds opt-in coherent memory, original string/scoreboard control flo
1448
 
1449
 
1450
  **RE-816:** Replacing the standard-decimal boundary with actual37914 exposes signed-minimum byte differences; prior modeled results remain explicitly separate. [Evidence](research/QVM_SCOREBOARD_FORMATTER.md).
 
 
 
 
1448
 
1449
 
1450
  **RE-816:** Replacing the standard-decimal boundary with actual37914 exposes signed-minimum byte differences; prior modeled results remain explicitly separate. [Evidence](research/QVM_SCOREBOARD_FORMATTER.md).
1451
+
1452
+
1453
+ **RE-817:** Rust now matches actual37914 rather than host printf for decimal/string/character dispatch, including signed-minimum, null precision and padding quirks. [Evidence](research/RUST_QVM_FORMAT.md).
docs/PROVISIONAL_TODO.md CHANGED
@@ -1458,3 +1458,6 @@ RE-815: 37914 now executes original dispatch tables and integer/string children;
1458
 
1459
 
1460
  RE-816: Replacing the standard-decimal boundary with actual37914 exposes signed-minimum byte differences; prior modeled results remain explicitly separate. Full runtime completion remains open. [Evidence](research/QVM_SCOREBOARD_FORMATTER.md).
 
 
 
 
1458
 
1459
 
1460
  RE-816: Replacing the standard-decimal boundary with actual37914 exposes signed-minimum byte differences; prior modeled results remain explicitly separate. Full runtime completion remains open. [Evidence](research/QVM_SCOREBOARD_FORMATTER.md).
1461
+
1462
+
1463
+ RE-817: Rust now matches actual37914 rather than host printf for decimal/string/character dispatch, including signed-minimum, null precision and padding quirks. Full runtime completion remains open. [Evidence](research/RUST_QVM_FORMAT.md).
docs/RESEARCH_LEDGER.md CHANGED
@@ -1748,3 +1748,8 @@ After exactly ten task commits from `744c236f`, the single `cargo xtask test-ful
1748
  ## 2026-10-27 — RE-816 Scoreboard packets with actual original formatter execution
1749
 
1750
  Replacing the standard-decimal boundary with actual37914 exposes signed-minimum byte differences; prior modeled results remain explicitly separate. Focused `cargo xtask test-group scoreboard-formatter` passes (5 Python tests); selected reports regenerate twice deterministically, with storage, documentation links and whitespace guards. Full suite deferred to task ten. Original assets unchanged; source fixtures do not establish complete native/runtime parity.
 
 
 
 
 
 
1748
  ## 2026-10-27 — RE-816 Scoreboard packets with actual original formatter execution
1749
 
1750
  Replacing the standard-decimal boundary with actual37914 exposes signed-minimum byte differences; prior modeled results remain explicitly separate. Focused `cargo xtask test-group scoreboard-formatter` passes (5 Python tests); selected reports regenerate twice deterministically, with storage, documentation links and whitespace guards. Full suite deferred to task ten. Original assets unchanged; source fixtures do not establish complete native/runtime parity.
1751
+
1752
+
1753
+ ## 2026-10-27 — RE-817 Rust recovered formatter subset with original byte quirks
1754
+
1755
+ Rust now matches actual37914 rather than host printf for decimal/string/character dispatch, including signed-minimum, null precision and padding quirks. Focused `cargo xtask test-group rust-qvm-format` passes (9 Python tests); selected reports regenerate twice deterministically, with storage, documentation links and whitespace guards. Full suite deferred to task ten. Original assets unchanged; source fixtures do not establish complete native/runtime parity.
docs/REVERSE_ENGINEERING_TODO.md CHANGED
@@ -1498,3 +1498,6 @@ Purpose: turn the supplied installation into an evidence-backed specification of
1498
 
1499
 
1500
  - [x] RE-816 Scoreboard packets with actual original formatter execution ([evidence](research/QVM_SCOREBOARD_FORMATTER.md)).
 
 
 
 
1498
 
1499
 
1500
  - [x] RE-816 Scoreboard packets with actual original formatter execution ([evidence](research/QVM_SCOREBOARD_FORMATTER.md)).
1501
+
1502
+
1503
+ - [x] RE-817 Rust recovered formatter subset with original byte quirks ([evidence](research/RUST_QVM_FORMAT.md)).
docs/research/README.md CHANGED
@@ -1038,3 +1038,6 @@ The extension collector adds five focused tests for native tail calls, command-r
1038
 
1039
 
1040
  [RE-816: Scoreboard packets with actual original formatter execution](QVM_SCOREBOARD_FORMATTER.md).
 
 
 
 
1038
 
1039
 
1040
  [RE-816: Scoreboard packets with actual original formatter execution](QVM_SCOREBOARD_FORMATTER.md).
1041
+
1042
+
1043
+ [RE-817: Rust recovered formatter subset with original byte quirks](RUST_QVM_FORMAT.md).
docs/research/RUST_QVM_FORMAT.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Rust recovered formatter subset with original byte quirks
2
+
3
+ RE-817. Reproduce with `python3 scripts/analyze_rust_qvm_format.py`; focused validation: `cargo xtask test-group rust-qvm-format`. [Machine report](rust-qvm-format.json).
4
+
5
+ A typed byte-oriented formatter reproduces55 original-QVM fixtures exactly. Unit tests cover negative zero padding, string trailing padding, ignored null precision, unknown conversion low bytes and embedded character NUL. Unsafe narrow-left padding, malformed arguments, float formatting and output/width limits reject explicitly. This stage leaves scoreboard packet integration to the next task.
6
+
7
+ Original executable/native runtime and complete Linux/macOS Rust/Bevy/Solari acceptance remain unverified.
docs/research/rust-health-impulse.json CHANGED
@@ -2985,7 +2985,7 @@
2985
  "exit_code": 2
2986
  }
2987
  ],
2988
- "rust_unit_tests": 71,
2989
  "source_report": "docs/research/qvm-health-impulse.json",
2990
  "source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
2991
  }
 
2985
  "exit_code": 2
2986
  }
2987
  ],
2988
+ "rust_unit_tests": 74,
2989
  "source_report": "docs/research/qvm-health-impulse.json",
2990
  "source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
2991
  }
docs/research/rust-qvm-format.json ADDED
@@ -0,0 +1,615 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "limits": "Rust recovered37914 integer/string/character subset matches55 complete original-QVM cases, including signed minimum and nonstandard flags. Float, unsafe narrow-left padding, malformed/unknown varargs reject explicitly; no native host printf claim.",
3
+ "probes": [
4
+ {
5
+ "rust": {
6
+ "count": 11,
7
+ "cstring_hex": "2d2e2f2c292c282d2a2c2800"
8
+ },
9
+ "source": {
10
+ "count": 11,
11
+ "cstring_hex": "2d2e2f2c292c282d2a2c2800"
12
+ },
13
+ "template": "%i"
14
+ },
15
+ {
16
+ "rust": {
17
+ "count": 3,
18
+ "cstring_hex": "2d313200"
19
+ },
20
+ "source": {
21
+ "count": 3,
22
+ "cstring_hex": "2d313200"
23
+ },
24
+ "template": "%i"
25
+ },
26
+ {
27
+ "rust": {
28
+ "count": 1,
29
+ "cstring_hex": "3000"
30
+ },
31
+ "source": {
32
+ "count": 1,
33
+ "cstring_hex": "3000"
34
+ },
35
+ "template": "%i"
36
+ },
37
+ {
38
+ "rust": {
39
+ "count": 3,
40
+ "cstring_hex": "31323300"
41
+ },
42
+ "source": {
43
+ "count": 3,
44
+ "cstring_hex": "31323300"
45
+ },
46
+ "template": "%i"
47
+ },
48
+ {
49
+ "rust": {
50
+ "count": 10,
51
+ "cstring_hex": "3231343734383336343700"
52
+ },
53
+ "source": {
54
+ "count": 10,
55
+ "cstring_hex": "3231343734383336343700"
56
+ },
57
+ "template": "%i"
58
+ },
59
+ {
60
+ "rust": {
61
+ "count": 11,
62
+ "cstring_hex": "2d2e2f2c292c282d2a2c2800"
63
+ },
64
+ "source": {
65
+ "count": 11,
66
+ "cstring_hex": "2d2e2f2c292c282d2a2c2800"
67
+ },
68
+ "template": "%d"
69
+ },
70
+ {
71
+ "rust": {
72
+ "count": 3,
73
+ "cstring_hex": "2d313200"
74
+ },
75
+ "source": {
76
+ "count": 3,
77
+ "cstring_hex": "2d313200"
78
+ },
79
+ "template": "%d"
80
+ },
81
+ {
82
+ "rust": {
83
+ "count": 1,
84
+ "cstring_hex": "3000"
85
+ },
86
+ "source": {
87
+ "count": 1,
88
+ "cstring_hex": "3000"
89
+ },
90
+ "template": "%d"
91
+ },
92
+ {
93
+ "rust": {
94
+ "count": 3,
95
+ "cstring_hex": "31323300"
96
+ },
97
+ "source": {
98
+ "count": 3,
99
+ "cstring_hex": "31323300"
100
+ },
101
+ "template": "%d"
102
+ },
103
+ {
104
+ "rust": {
105
+ "count": 10,
106
+ "cstring_hex": "3231343734383336343700"
107
+ },
108
+ "source": {
109
+ "count": 10,
110
+ "cstring_hex": "3231343734383336343700"
111
+ },
112
+ "template": "%d"
113
+ },
114
+ {
115
+ "rust": {
116
+ "count": 11,
117
+ "cstring_hex": "2d2e2f2c292c282d2a2c2800"
118
+ },
119
+ "source": {
120
+ "count": 11,
121
+ "cstring_hex": "2d2e2f2c292c282d2a2c2800"
122
+ },
123
+ "template": "%p"
124
+ },
125
+ {
126
+ "rust": {
127
+ "count": 3,
128
+ "cstring_hex": "2d313200"
129
+ },
130
+ "source": {
131
+ "count": 3,
132
+ "cstring_hex": "2d313200"
133
+ },
134
+ "template": "%p"
135
+ },
136
+ {
137
+ "rust": {
138
+ "count": 1,
139
+ "cstring_hex": "3000"
140
+ },
141
+ "source": {
142
+ "count": 1,
143
+ "cstring_hex": "3000"
144
+ },
145
+ "template": "%p"
146
+ },
147
+ {
148
+ "rust": {
149
+ "count": 3,
150
+ "cstring_hex": "31323300"
151
+ },
152
+ "source": {
153
+ "count": 3,
154
+ "cstring_hex": "31323300"
155
+ },
156
+ "template": "%p"
157
+ },
158
+ {
159
+ "rust": {
160
+ "count": 10,
161
+ "cstring_hex": "3231343734383336343700"
162
+ },
163
+ "source": {
164
+ "count": 10,
165
+ "cstring_hex": "3231343734383336343700"
166
+ },
167
+ "template": "%p"
168
+ },
169
+ {
170
+ "rust": {
171
+ "count": 11,
172
+ "cstring_hex": "2d2e2f2c292c282d2a2c2800"
173
+ },
174
+ "source": {
175
+ "count": 11,
176
+ "cstring_hex": "2d2e2f2c292c282d2a2c2800"
177
+ },
178
+ "template": "%05i"
179
+ },
180
+ {
181
+ "rust": {
182
+ "count": 5,
183
+ "cstring_hex": "30302d313200"
184
+ },
185
+ "source": {
186
+ "count": 5,
187
+ "cstring_hex": "30302d313200"
188
+ },
189
+ "template": "%05i"
190
+ },
191
+ {
192
+ "rust": {
193
+ "count": 5,
194
+ "cstring_hex": "303030303000"
195
+ },
196
+ "source": {
197
+ "count": 5,
198
+ "cstring_hex": "303030303000"
199
+ },
200
+ "template": "%05i"
201
+ },
202
+ {
203
+ "rust": {
204
+ "count": 5,
205
+ "cstring_hex": "303031323300"
206
+ },
207
+ "source": {
208
+ "count": 5,
209
+ "cstring_hex": "303031323300"
210
+ },
211
+ "template": "%05i"
212
+ },
213
+ {
214
+ "rust": {
215
+ "count": 10,
216
+ "cstring_hex": "3231343734383336343700"
217
+ },
218
+ "source": {
219
+ "count": 10,
220
+ "cstring_hex": "3231343734383336343700"
221
+ },
222
+ "template": "%05i"
223
+ },
224
+ {
225
+ "rust": {
226
+ "count": 12,
227
+ "cstring_hex": "2d2e2f2c292c282d2a2c282000"
228
+ },
229
+ "source": {
230
+ "count": 12,
231
+ "cstring_hex": "2d2e2f2c292c282d2a2c282000"
232
+ },
233
+ "template": "%-12i"
234
+ },
235
+ {
236
+ "rust": {
237
+ "count": 12,
238
+ "cstring_hex": "2d313220202020202020202000"
239
+ },
240
+ "source": {
241
+ "count": 12,
242
+ "cstring_hex": "2d313220202020202020202000"
243
+ },
244
+ "template": "%-12i"
245
+ },
246
+ {
247
+ "rust": {
248
+ "count": 12,
249
+ "cstring_hex": "30202020202020202020202000"
250
+ },
251
+ "source": {
252
+ "count": 12,
253
+ "cstring_hex": "30202020202020202020202000"
254
+ },
255
+ "template": "%-12i"
256
+ },
257
+ {
258
+ "rust": {
259
+ "count": 12,
260
+ "cstring_hex": "31323320202020202020202000"
261
+ },
262
+ "source": {
263
+ "count": 12,
264
+ "cstring_hex": "31323320202020202020202000"
265
+ },
266
+ "template": "%-12i"
267
+ },
268
+ {
269
+ "rust": {
270
+ "count": 12,
271
+ "cstring_hex": "32313437343833363437202000"
272
+ },
273
+ "source": {
274
+ "count": 12,
275
+ "cstring_hex": "32313437343833363437202000"
276
+ },
277
+ "template": "%-12i"
278
+ },
279
+ {
280
+ "rust": {
281
+ "count": 11,
282
+ "cstring_hex": "2d2e2f2c292c282d2a2c2800"
283
+ },
284
+ "source": {
285
+ "count": 11,
286
+ "cstring_hex": "2d2e2f2c292c282d2a2c2800"
287
+ },
288
+ "template": "%.2i"
289
+ },
290
+ {
291
+ "rust": {
292
+ "count": 3,
293
+ "cstring_hex": "2d313200"
294
+ },
295
+ "source": {
296
+ "count": 3,
297
+ "cstring_hex": "2d313200"
298
+ },
299
+ "template": "%.2i"
300
+ },
301
+ {
302
+ "rust": {
303
+ "count": 1,
304
+ "cstring_hex": "3000"
305
+ },
306
+ "source": {
307
+ "count": 1,
308
+ "cstring_hex": "3000"
309
+ },
310
+ "template": "%.2i"
311
+ },
312
+ {
313
+ "rust": {
314
+ "count": 3,
315
+ "cstring_hex": "31323300"
316
+ },
317
+ "source": {
318
+ "count": 3,
319
+ "cstring_hex": "31323300"
320
+ },
321
+ "template": "%.2i"
322
+ },
323
+ {
324
+ "rust": {
325
+ "count": 10,
326
+ "cstring_hex": "3231343734383336343700"
327
+ },
328
+ "source": {
329
+ "count": 10,
330
+ "cstring_hex": "3231343734383336343700"
331
+ },
332
+ "template": "%.2i"
333
+ },
334
+ {
335
+ "rust": {
336
+ "count": 0,
337
+ "cstring_hex": "00"
338
+ },
339
+ "source": {
340
+ "count": 0,
341
+ "cstring_hex": "00"
342
+ },
343
+ "template": "%s"
344
+ },
345
+ {
346
+ "rust": {
347
+ "count": 1,
348
+ "cstring_hex": "4100"
349
+ },
350
+ "source": {
351
+ "count": 1,
352
+ "cstring_hex": "4100"
353
+ },
354
+ "template": "%s"
355
+ },
356
+ {
357
+ "rust": {
358
+ "count": 5,
359
+ "cstring_hex": "414243444500"
360
+ },
361
+ "source": {
362
+ "count": 5,
363
+ "cstring_hex": "414243444500"
364
+ },
365
+ "template": "%s"
366
+ },
367
+ {
368
+ "rust": {
369
+ "count": 3,
370
+ "cstring_hex": "fffefd00"
371
+ },
372
+ "source": {
373
+ "count": 3,
374
+ "cstring_hex": "fffefd00"
375
+ },
376
+ "template": "%s"
377
+ },
378
+ {
379
+ "rust": {
380
+ "count": 6,
381
+ "cstring_hex": "286e756c6c2900"
382
+ },
383
+ "source": {
384
+ "count": 6,
385
+ "cstring_hex": "286e756c6c2900"
386
+ },
387
+ "template": "%s"
388
+ },
389
+ {
390
+ "rust": {
391
+ "count": 12,
392
+ "cstring_hex": "20202020202020202020202000"
393
+ },
394
+ "source": {
395
+ "count": 12,
396
+ "cstring_hex": "20202020202020202020202000"
397
+ },
398
+ "template": "%12s"
399
+ },
400
+ {
401
+ "rust": {
402
+ "count": 12,
403
+ "cstring_hex": "41202020202020202020202000"
404
+ },
405
+ "source": {
406
+ "count": 12,
407
+ "cstring_hex": "41202020202020202020202000"
408
+ },
409
+ "template": "%12s"
410
+ },
411
+ {
412
+ "rust": {
413
+ "count": 12,
414
+ "cstring_hex": "41424344452020202020202000"
415
+ },
416
+ "source": {
417
+ "count": 12,
418
+ "cstring_hex": "41424344452020202020202000"
419
+ },
420
+ "template": "%12s"
421
+ },
422
+ {
423
+ "rust": {
424
+ "count": 12,
425
+ "cstring_hex": "fffefd20202020202020202000"
426
+ },
427
+ "source": {
428
+ "count": 12,
429
+ "cstring_hex": "fffefd20202020202020202000"
430
+ },
431
+ "template": "%12s"
432
+ },
433
+ {
434
+ "rust": {
435
+ "count": 12,
436
+ "cstring_hex": "286e756c6c2920202020202000"
437
+ },
438
+ "source": {
439
+ "count": 12,
440
+ "cstring_hex": "286e756c6c2920202020202000"
441
+ },
442
+ "template": "%12s"
443
+ },
444
+ {
445
+ "rust": {
446
+ "count": 0,
447
+ "cstring_hex": "00"
448
+ },
449
+ "source": {
450
+ "count": 0,
451
+ "cstring_hex": "00"
452
+ },
453
+ "template": "%.2s"
454
+ },
455
+ {
456
+ "rust": {
457
+ "count": 1,
458
+ "cstring_hex": "4100"
459
+ },
460
+ "source": {
461
+ "count": 1,
462
+ "cstring_hex": "4100"
463
+ },
464
+ "template": "%.2s"
465
+ },
466
+ {
467
+ "rust": {
468
+ "count": 2,
469
+ "cstring_hex": "414200"
470
+ },
471
+ "source": {
472
+ "count": 2,
473
+ "cstring_hex": "414200"
474
+ },
475
+ "template": "%.2s"
476
+ },
477
+ {
478
+ "rust": {
479
+ "count": 2,
480
+ "cstring_hex": "fffe00"
481
+ },
482
+ "source": {
483
+ "count": 2,
484
+ "cstring_hex": "fffe00"
485
+ },
486
+ "template": "%.2s"
487
+ },
488
+ {
489
+ "rust": {
490
+ "count": 6,
491
+ "cstring_hex": "286e756c6c2900"
492
+ },
493
+ "source": {
494
+ "count": 6,
495
+ "cstring_hex": "286e756c6c2900"
496
+ },
497
+ "template": "%.2s"
498
+ },
499
+ {
500
+ "rust": {
501
+ "count": 8,
502
+ "cstring_hex": "202020202020202000"
503
+ },
504
+ "source": {
505
+ "count": 8,
506
+ "cstring_hex": "202020202020202000"
507
+ },
508
+ "template": "%08.2s"
509
+ },
510
+ {
511
+ "rust": {
512
+ "count": 8,
513
+ "cstring_hex": "412020202020202000"
514
+ },
515
+ "source": {
516
+ "count": 8,
517
+ "cstring_hex": "412020202020202000"
518
+ },
519
+ "template": "%08.2s"
520
+ },
521
+ {
522
+ "rust": {
523
+ "count": 8,
524
+ "cstring_hex": "414220202020202000"
525
+ },
526
+ "source": {
527
+ "count": 8,
528
+ "cstring_hex": "414220202020202000"
529
+ },
530
+ "template": "%08.2s"
531
+ },
532
+ {
533
+ "rust": {
534
+ "count": 8,
535
+ "cstring_hex": "fffe20202020202000"
536
+ },
537
+ "source": {
538
+ "count": 8,
539
+ "cstring_hex": "fffe20202020202000"
540
+ },
541
+ "template": "%08.2s"
542
+ },
543
+ {
544
+ "rust": {
545
+ "count": 8,
546
+ "cstring_hex": "286e756c6c29202000"
547
+ },
548
+ "source": {
549
+ "count": 8,
550
+ "cstring_hex": "286e756c6c29202000"
551
+ },
552
+ "template": "%08.2s"
553
+ },
554
+ {
555
+ "rust": {
556
+ "count": 0,
557
+ "cstring_hex": "00"
558
+ },
559
+ "source": {
560
+ "count": 0,
561
+ "cstring_hex": "00"
562
+ },
563
+ "template": ""
564
+ },
565
+ {
566
+ "rust": {
567
+ "count": 13,
568
+ "cstring_hex": "706c61696e206c69746572616c00"
569
+ },
570
+ "source": {
571
+ "count": 13,
572
+ "cstring_hex": "706c61696e206c69746572616c00"
573
+ },
574
+ "template": "plain literal"
575
+ },
576
+ {
577
+ "rust": {
578
+ "count": 5,
579
+ "cstring_hex": "253a413a4200"
580
+ },
581
+ "source": {
582
+ "count": 5,
583
+ "cstring_hex": "253a413a4200"
584
+ },
585
+ "template": "%%:%c:%q"
586
+ },
587
+ {
588
+ "rust": {
589
+ "count": 3,
590
+ "cstring_hex": "0020ff00"
591
+ },
592
+ "source": {
593
+ "count": 3,
594
+ "cstring_hex": "0020ff00"
595
+ },
596
+ "template": "%c %c"
597
+ },
598
+ {
599
+ "rust": {
600
+ "count": 18,
601
+ "cstring_hex": "68656c6c6f202d3132202f2041202573204200"
602
+ },
603
+ "source": {
604
+ "count": 18,
605
+ "cstring_hex": "68656c6c6f202d3132202f2041202573204200"
606
+ },
607
+ "template": "hello %i / %s"
608
+ }
609
+ ],
610
+ "rejections": [
611
+ 2
612
+ ],
613
+ "source_report": "docs/research/qvm-formatter.json",
614
+ "source_report_sha256": "05948658242afbba756f1c33448be002c60cc73b2a108ec605140ec4b39de31d"
615
+ }
scripts/analyze_rust_qvm_format.py ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ import hashlib,json
3
+ from pathlib import Path
4
+ from rust_lifecycle_support import binary,result,invoke
5
+ from analyze_qvm import write_json
6
+
7
+ def encode(p):
8
+ t=p['template'].encode();out=[len(t),len(p['arguments']),*t]
9
+ for a in p['arguments']:
10
+ if 'word' in a:out += [0,a['word']]
11
+ elif 'null' in a:out += [2]
12
+ else:b=bytes.fromhex(a['bytes_hex']);out += [1,len(b),*b]
13
+ return out
14
+
15
+ def main():
16
+ b=binary();p=Path('docs/research/qvm-formatter.json');data=p.read_bytes();rows=[]
17
+ for s in json.loads(data)['probes']:rows.append({'template':s['template'],'source':{'cstring_hex':s['output_hex'],'count':s['count']},'rust':result(b,'qvm-format',encode(s))})
18
+ bad=invoke(b,'qvm-format',encode({'template':'%-1i','arguments':[{'word':123}]}),check=False);assert bad.returncode
19
+ write_json(Path('docs/research/rust-qvm-format.json'),{'source_report':str(p),'source_report_sha256':hashlib.sha256(data).hexdigest(),'probes':rows,'rejections':[bad.returncode],'limits':'Rust recovered37914 integer/string/character subset matches55 complete original-QVM cases, including signed minimum and nonstandard flags. Float, unsafe narrow-left padding, malformed/unknown varargs reject explicitly; no native host printf claim.'})
20
+ if __name__=='__main__':main()
scripts/test_rust_qvm_format.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib,json,unittest
2
+ from pathlib import Path
3
+ class RustQvmFormatTests(unittest.TestCase):
4
+ def test_exact_original_formatter_bytes_and_counts(self):
5
+ r=json.loads(Path('docs/research/rust-qvm-format.json').read_text());self.assertEqual(len(r['probes']),55)
6
+ for p in r['probes']:self.assertEqual(p['rust'],p['source'],p['template'])
7
+ def test_identity_and_padding_domain_rejection(self):
8
+ r=json.loads(Path('docs/research/rust-qvm-format.json').read_text());self.assertEqual(hashlib.sha256(Path(r['source_report']).read_bytes()).hexdigest(),r['source_report_sha256']);self.assertTrue(all(r['rejections']))
9
+ if __name__=='__main__':unittest.main()
xtask/checks.tsv CHANGED
@@ -1987,3 +1987,16 @@ report scripts/analyze_qvm_scoreboard_formatter.py docs/research/qvm-scoreboard-
1987
  edge docs/research/qvm-scoreboard-formatter.json scripts/test_qvm_scoreboard_formatter.py
1988
  group scoreboard-formatter scripts/analyze_qvm_scoreboard_formatter.py
1989
  edge scripts/analyze_qvm_scoreboard_formatter.py scripts/analyze_qvm_resumer_audit.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1987
  edge docs/research/qvm-scoreboard-formatter.json scripts/test_qvm_scoreboard_formatter.py
1988
  group scoreboard-formatter scripts/analyze_qvm_scoreboard_formatter.py
1989
  edge scripts/analyze_qvm_scoreboard_formatter.py scripts/analyze_qvm_resumer_audit.py
1990
+ edge Cargo.toml scripts/analyze_rust_qvm_format.py
1991
+ edge Cargo.lock scripts/analyze_rust_qvm_format.py
1992
+ edge crates/urt-gameplay/Cargo.toml scripts/analyze_rust_qvm_format.py
1993
+ edge crates/urt-gameplay/src/lib.rs scripts/analyze_rust_qvm_format.py
1994
+ edge crates/urt-gameplay/src/bin/urt-lifecycle.rs scripts/analyze_rust_qvm_format.py
1995
+ edge crates/urt-gameplay/src/qvm_format.rs scripts/analyze_rust_qvm_format.py
1996
+ edge crates/urt-gameplay/src/qvm_format.rs scripts/analyze_rust_health_impulse.py
1997
+ group gameplay scripts/analyze_rust_qvm_format.py
1998
+ group rust-qvm-format scripts/analyze_rust_health_impulse.py
1999
+ report scripts/analyze_rust_qvm_format.py docs/research/rust-qvm-format.json
2000
+ edge docs/research/rust-qvm-format.json scripts/test_rust_qvm_format.py
2001
+ group rust-qvm-format scripts/analyze_rust_qvm_format.py
2002
+ edge scripts/analyze_rust_qvm_format.py scripts/analyze_qvm_resumer_audit.py
xtask/src/lib.rs CHANGED
@@ -1045,6 +1045,7 @@ mod integration_tests {
1045
  ("ranking_population", "ranking_population"),
1046
  ("scoreboard", "scoreboard"),
1047
  ("scoreboard_wire", "scoreboard_rows"),
 
1048
  ("leaderboard", "leaderboard"),
1049
  ("recipient_wire", "recipient_wire"),
1050
  ] {
 
1045
  ("ranking_population", "ranking_population"),
1046
  ("scoreboard", "scoreboard"),
1047
  ("scoreboard_wire", "scoreboard_rows"),
1048
+ ("qvm_format", "qvm_format"),
1049
  ("leaderboard", "leaderboard"),
1050
  ("recipient_wire", "recipient_wire"),
1051
  ] {