nmarshall commited on
Commit
40de183
·
1 Parent(s): f43ec68

gameplay: add in-memory persistence round trips

Browse files
crates/urt-gameplay/src/bin/urt-native-policy.rs CHANGED
@@ -653,6 +653,66 @@ fn run() -> Result<String, String> {
653
  command
654
  ))
655
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
656
  _ => Err("unknown command".into()),
657
  }
658
  }
 
653
  command
654
  ))
655
  }
656
+ "roundtrip" => {
657
+ if w.len() < 85 {
658
+ return Err("missing roundtrip header".into());
659
+ }
660
+ let n = w[5] as usize;
661
+ if n > 64 || w.len() != 85 + n {
662
+ return Err("invalid roundtrip count".into());
663
+ }
664
+ let mut engine = MemoryPersistence::default();
665
+ let mut target = RestoreState {
666
+ values: [0; 9],
667
+ ignore: [2; 64],
668
+ position: [0; 6],
669
+ };
670
+ let mut selector = 0;
671
+ let indices = w[85..].iter().map(|v| *v as usize).collect::<Vec<_>>();
672
+ let out = persistence_roundtrip(
673
+ RoundtripInput {
674
+ position: PositionInput {
675
+ mode: w[0] as i32,
676
+ enabled: 1,
677
+ slot: w[1] as usize,
678
+ userinfo: b"\\cl_guid\\ID",
679
+ mapname: b"ut4_test",
680
+ name: b"PLAYER",
681
+ values: w[79..85].try_into().unwrap(),
682
+ handle: w[2],
683
+ },
684
+ flags: w[3],
685
+ values: std::array::from_fn(|j| w[6 + j] as i32),
686
+ ignore: std::array::from_fn(|j| w[15 + j] as i32),
687
+ entity_clients: &indices,
688
+ scratch: [w[4]; 5],
689
+ read_handle: 7,
690
+ },
691
+ &mut target,
692
+ &mut engine,
693
+ &mut selector,
694
+ )
695
+ .map_err(|e| format!("{e:?}"))?;
696
+ let file = engine
697
+ .files
698
+ .values()
699
+ .next()
700
+ .map(|v| format!("\"{}\"", hex(v)))
701
+ .unwrap_or("null".into());
702
+ Ok(format!(
703
+ "{{\"saved\":{},\"sets\":{},\"file\":{},\"values\":{},\"ignore\":{},\"origin\":{},\"scan_result\":{},\"cvars\":{},\"load_events\":{},\"ring\":{},\"info_ring\":{selector}}}",
704
+ position_events(&out.saved.events),
705
+ sets_json(&out.written.sets),
706
+ file,
707
+ list(&target.values.map(|v| v as u32)),
708
+ list(&target.ignore.map(|v| v as u32)),
709
+ list(&target.position),
710
+ out.restored.scan_result,
711
+ sets_json(&out.restored.cvars),
712
+ position_events(&out.restored.position.io.events),
713
+ out.ring_advance
714
+ ))
715
+ }
716
  _ => Err("unknown command".into()),
717
  }
718
  }
crates/urt-gameplay/src/native_policy.rs CHANGED
@@ -902,6 +902,7 @@ pub struct PositionIo {
902
  pub events: Vec<PositionEvent>,
903
  pub ring_advance: u32,
904
  }
 
905
  pub struct PositionInput<'a> {
906
  pub mode: i32,
907
  pub enabled: i32,
@@ -1719,3 +1720,177 @@ mod normal_persist_tests {
1719
  assert_eq!(ring, 1);
1720
  }
1721
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
902
  pub events: Vec<PositionEvent>,
903
  pub ring_advance: u32,
904
  }
905
+ #[derive(Clone, Copy)]
906
  pub struct PositionInput<'a> {
907
  pub mode: i32,
908
  pub enabled: i32,
 
1720
  assert_eq!(ring, 1);
1721
  }
1722
  }
1723
+
1724
+ #[derive(Clone, Debug, Default, PartialEq, Eq)]
1725
+ pub struct MemoryPersistence {
1726
+ pub cvars: std::collections::BTreeMap<Vec<u8>, Vec<u8>>,
1727
+ pub files: std::collections::BTreeMap<Vec<u8>, Vec<u8>>,
1728
+ }
1729
+ impl MemoryPersistence {
1730
+ /// Apply a single captured saved-position write sequence to an in-memory
1731
+ /// transcript. This owns bytes, not native handles or operating-system files.
1732
+ pub fn apply_position(&mut self, io: &PositionIo) {
1733
+ let mut path = None;
1734
+ for e in &io.events {
1735
+ match e {
1736
+ PositionEvent::Open { path: p, mode: 1 } => path = Some(p.clone()),
1737
+ PositionEvent::Write { data, .. } => {
1738
+ if let Some(p) = &path {
1739
+ self.files.insert(p.clone(), data.clone());
1740
+ }
1741
+ }
1742
+ _ => {}
1743
+ }
1744
+ }
1745
+ }
1746
+ pub fn apply_sets(&mut self, sets: &[(Vec<u8>, Vec<u8>)]) {
1747
+ self.cvars.extend(sets.iter().cloned());
1748
+ }
1749
+ }
1750
+ pub struct RoundtripInput<'a> {
1751
+ pub position: PositionInput<'a>,
1752
+ pub flags: u32,
1753
+ pub values: [i32; 9],
1754
+ pub ignore: [i32; 64],
1755
+ pub entity_clients: &'a [usize],
1756
+ pub scratch: [u32; 5],
1757
+ pub read_handle: u32,
1758
+ }
1759
+ #[derive(Clone, Debug, PartialEq, Eq)]
1760
+ pub struct PersistenceRoundtrip {
1761
+ pub saved: PositionIo,
1762
+ pub written: SessionWrite,
1763
+ pub restored: RestoredSession,
1764
+ pub ring_advance: u32,
1765
+ }
1766
+ pub fn persistence_roundtrip(
1767
+ input: RoundtripInput<'_>,
1768
+ target: &mut RestoreState,
1769
+ engine: &mut MemoryPersistence,
1770
+ info_ring: &mut u32,
1771
+ ) -> Result<PersistenceRoundtrip, Error> {
1772
+ if input.entity_clients.len() > 64 {
1773
+ return Err(Error::InvalidCount);
1774
+ }
1775
+ let mut staged_target = target.clone();
1776
+ let mut staged_engine = engine.clone();
1777
+ let mut selector = *info_ring;
1778
+ let context = input.position;
1779
+ let saved = save_position(context, &mut selector)?;
1780
+ let written = session_write(
1781
+ context.slot,
1782
+ input.entity_clients.len() as i32,
1783
+ input.values,
1784
+ input.entity_clients,
1785
+ &input.ignore,
1786
+ )?;
1787
+ staged_engine.apply_position(&saved);
1788
+ staged_engine.apply_sets(&written.sets);
1789
+ let mut unused = 0;
1790
+ let raw = cbytes(context.userinfo);
1791
+ let guid = info_lookup(
1792
+ Some(&raw[..raw.len().min(1023)]),
1793
+ Some(b"cl_guid"),
1794
+ &mut unused,
1795
+ )?;
1796
+ let path = position_path(context.mapname, &guid.value);
1797
+ let file = staged_engine.files.get(&path).cloned();
1798
+ let data = file.as_deref().unwrap_or_default();
1799
+ let length = if file.is_some() {
1800
+ i32::try_from(data.len()).map_err(|_| Error::InvalidInput)?
1801
+ } else {
1802
+ -1
1803
+ };
1804
+ let mut load = context;
1805
+ load.handle = if file.is_some() { input.read_handle } else { 0 };
1806
+ let session = staged_engine
1807
+ .cvars
1808
+ .get(format!("session{}", context.slot).as_bytes())
1809
+ .cloned()
1810
+ .unwrap_or_default();
1811
+ let ignored = staged_engine
1812
+ .cvars
1813
+ .get(format!("sessIgnore{}", context.slot).as_bytes())
1814
+ .cloned()
1815
+ .unwrap_or_default();
1816
+ let restored = restore_session(
1817
+ &mut staged_target,
1818
+ RestoreInput {
1819
+ flags: input.flags,
1820
+ session: &session,
1821
+ ignore_text: &ignored,
1822
+ scratch: input.scratch,
1823
+ position: load,
1824
+ file_length: length,
1825
+ file_data: data,
1826
+ },
1827
+ &mut selector,
1828
+ )?;
1829
+ let ring_advance = saved.ring_advance + written.ring_advance + restored.ring_advance;
1830
+ *target = staged_target;
1831
+ *engine = staged_engine;
1832
+ *info_ring = selector;
1833
+ Ok(PersistenceRoundtrip {
1834
+ saved,
1835
+ written,
1836
+ restored,
1837
+ ring_advance,
1838
+ })
1839
+ }
1840
+ #[cfg(test)]
1841
+ mod memory_persistence_tests {
1842
+ use super::*;
1843
+ #[test]
1844
+ fn zero_handle_open_does_not_create_file() {
1845
+ let mut engine = MemoryPersistence::default();
1846
+ engine.apply_position(&PositionIo {
1847
+ events: vec![PositionEvent::Open {
1848
+ path: b"x".to_vec(),
1849
+ mode: 1,
1850
+ }],
1851
+ ring_advance: 1,
1852
+ });
1853
+ assert!(engine.files.is_empty());
1854
+ }
1855
+ #[test]
1856
+ fn rejected_indices_do_not_commit_engine_or_selector() {
1857
+ let mut engine = MemoryPersistence::default();
1858
+ let old = engine.clone();
1859
+ let mut target = RestoreState {
1860
+ values: [0; 9],
1861
+ ignore: [2; 64],
1862
+ position: [0; 6],
1863
+ };
1864
+ let before = target.clone();
1865
+ let mut selector = 0;
1866
+ assert_eq!(
1867
+ persistence_roundtrip(
1868
+ RoundtripInput {
1869
+ position: PositionInput {
1870
+ mode: 0,
1871
+ enabled: 1,
1872
+ slot: 0,
1873
+ userinfo: b"",
1874
+ mapname: b"m",
1875
+ name: b"n",
1876
+ values: [0; 6],
1877
+ handle: 0
1878
+ },
1879
+ flags: 0,
1880
+ values: [0; 9],
1881
+ ignore: [0; 64],
1882
+ entity_clients: &[64],
1883
+ scratch: [0; 5],
1884
+ read_handle: 7
1885
+ },
1886
+ &mut target,
1887
+ &mut engine,
1888
+ &mut selector
1889
+ ),
1890
+ Err(Error::InvalidIndex)
1891
+ );
1892
+ assert_eq!(engine, old);
1893
+ assert_eq!(target, before);
1894
+ assert_eq!(selector, 0);
1895
+ }
1896
+ }
docs/FINDINGS.md CHANGED
@@ -1730,3 +1730,6 @@ RE-813–822 replaces the scoreboard decimal assumption with actual37914 executi
1730
 
1731
 
1732
  **RE-910:** 144 finite original246746/246963/247219/246554 compositions feed actual captured cvar/file bytes into a fresh destination snapshot. Negative and INT_MIN integer formatting is deliberately not assumed invertible; original partial scanner writes and supplied scratch are preserved. Ignore entries accumulate over the destination table, and six-float file prefixes survive appended names. The engine is a supplied in-memory transcript, not a native filesystem or real map reload. [Evidence](research/QVM_PERSISTENCE_ROUNDTRIP.md).
 
 
 
 
1730
 
1731
 
1732
  **RE-910:** 144 finite original246746/246963/247219/246554 compositions feed actual captured cvar/file bytes into a fresh destination snapshot. Negative and INT_MIN integer formatting is deliberately not assumed invertible; original partial scanner writes and supplied scratch are preserved. Ignore entries accumulate over the destination table, and six-float file prefixes survive appended names. The engine is a supplied in-memory transcript, not a native filesystem or real map reload. [Evidence](research/QVM_PERSISTENCE_ROUNDTRIP.md).
1733
+
1734
+
1735
+ **RE-911:** Rust matches144 original save/session-write/restore/read compositions using owned in-memory cvar/file bytes. Partial integer parsing and caller scratch preserve the observed non-invertibility of negative/INT_MIN session text; file prefixes and ignore accumulation remain exact. Engine/destination/selector changes stage atomically on errors. This adapter does not own native handles or perform OS file IO. [Evidence](research/RUST_PERSISTENCE_ROUNDTRIP.md).
docs/PROVISIONAL_TODO.md CHANGED
@@ -1767,3 +1767,6 @@ RE-909: Rust matches64 original normal transitions with actual saved-file and cv
1767
 
1768
 
1769
  RE-910: 144 finite original246746/246963/247219/246554 compositions feed actual captured cvar/file bytes into a fresh destination snapshot. Negative and INT_MIN integer formatting is deliberately not assumed invertible; original partial scanner writes and supplied scratch are preserved. Ignore entries accumulate over the destination table, and six-float file prefixes survive appended names. The engine is a supplied in-memory transcript, not a native filesystem or real map reload. Full runtime completion remains open. [Evidence](research/QVM_PERSISTENCE_ROUNDTRIP.md).
 
 
 
 
1767
 
1768
 
1769
  RE-910: 144 finite original246746/246963/247219/246554 compositions feed actual captured cvar/file bytes into a fresh destination snapshot. Negative and INT_MIN integer formatting is deliberately not assumed invertible; original partial scanner writes and supplied scratch are preserved. Ignore entries accumulate over the destination table, and six-float file prefixes survive appended names. The engine is a supplied in-memory transcript, not a native filesystem or real map reload. Full runtime completion remains open. [Evidence](research/QVM_PERSISTENCE_ROUNDTRIP.md).
1770
+
1771
+
1772
+ RE-911: Rust matches144 original save/session-write/restore/read compositions using owned in-memory cvar/file bytes. Partial integer parsing and caller scratch preserve the observed non-invertibility of negative/INT_MIN session text; file prefixes and ignore accumulation remain exact. Engine/destination/selector changes stage atomically on errors. This adapter does not own native handles or perform OS file IO. Full runtime completion remains open. [Evidence](research/RUST_PERSISTENCE_ROUNDTRIP.md).
docs/RESEARCH_LEDGER.md CHANGED
@@ -2270,3 +2270,8 @@ Rust matches64 original normal transitions with actual saved-file and cvar child
2270
  ## 2026-10-28 — RE-910 Original write-to-restore persistence round trip
2271
 
2272
  144 finite original246746/246963/247219/246554 compositions feed actual captured cvar/file bytes into a fresh destination snapshot. Negative and INT_MIN integer formatting is deliberately not assumed invertible; original partial scanner writes and supplied scratch are preserved. Ignore entries accumulate over the destination table, and six-float file prefixes survive appended names. The engine is a supplied in-memory transcript, not a native filesystem or real map reload. Focused `cargo xtask test-group qvm-persistence-roundtrip` passes (4 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.
 
 
 
 
 
 
2270
  ## 2026-10-28 — RE-910 Original write-to-restore persistence round trip
2271
 
2272
  144 finite original246746/246963/247219/246554 compositions feed actual captured cvar/file bytes into a fresh destination snapshot. Negative and INT_MIN integer formatting is deliberately not assumed invertible; original partial scanner writes and supplied scratch are preserved. Ignore entries accumulate over the destination table, and six-float file prefixes survive appended names. The engine is a supplied in-memory transcript, not a native filesystem or real map reload. Focused `cargo xtask test-group qvm-persistence-roundtrip` passes (4 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.
2273
+
2274
+
2275
+ ## 2026-10-28 — RE-911 Rust in-memory persistence round trip
2276
+
2277
+ Rust matches144 original save/session-write/restore/read compositions using owned in-memory cvar/file bytes. Partial integer parsing and caller scratch preserve the observed non-invertibility of negative/INT_MIN session text; file prefixes and ignore accumulation remain exact. Engine/destination/selector changes stage atomically on errors. This adapter does not own native handles or perform OS file IO. Focused `cargo xtask test-group rust-persistence-roundtrip` 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
@@ -1804,3 +1804,6 @@ Purpose: turn the supplied installation into an evidence-backed specification of
1804
 
1805
 
1806
  - [x] RE-910 Original write-to-restore persistence round trip ([evidence](research/QVM_PERSISTENCE_ROUNDTRIP.md)).
 
 
 
 
1804
 
1805
 
1806
  - [x] RE-910 Original write-to-restore persistence round trip ([evidence](research/QVM_PERSISTENCE_ROUNDTRIP.md)).
1807
+
1808
+
1809
+ - [x] RE-911 Rust in-memory persistence round trip ([evidence](research/RUST_PERSISTENCE_ROUNDTRIP.md)).
docs/research/README.md CHANGED
@@ -1339,3 +1339,6 @@ The extension collector adds five focused tests for native tail calls, command-r
1339
 
1340
 
1341
  [RE-910: Original write-to-restore persistence round trip](QVM_PERSISTENCE_ROUNDTRIP.md).
 
 
 
 
1339
 
1340
 
1341
  [RE-910: Original write-to-restore persistence round trip](QVM_PERSISTENCE_ROUNDTRIP.md).
1342
+
1343
+
1344
+ [RE-911: Rust in-memory persistence round trip](RUST_PERSISTENCE_ROUNDTRIP.md).
docs/research/RUST_PERSISTENCE_ROUNDTRIP.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Rust in-memory persistence round trip
2
+
3
+ RE-911. Reproduce with `python3 scripts/analyze_rust_persistence_roundtrip.py`; focused validation: `cargo xtask test-group rust-persistence-roundtrip`. [Machine report](rust-persistence-roundtrip.json).
4
+
5
+ Rust matches144 original save/session-write/restore/read compositions using owned in-memory cvar/file bytes. Partial integer parsing and caller scratch preserve the observed non-invertibility of negative/INT_MIN session text; file prefixes and ignore accumulation remain exact. Engine/destination/selector changes stage atomically on errors. This adapter does not own native handles or perform OS file IO.
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": 163,
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": 165,
2989
  "source_report": "docs/research/qvm-health-impulse.json",
2990
  "source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
2991
  }
docs/research/rust-persistence-roundtrip.json ADDED
The diff for this file is too large to render. See raw diff
 
scripts/analyze_rust_persistence_roundtrip.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ import hashlib,json
3
+ from pathlib import Path
4
+ from rust_native_policy_support import binary,result,invoke
5
+ from analyze_qvm import write_json
6
+ def encode(s):
7
+ a=s['input'];return [a['mode'],a['slot'],7 if a['opened'] else 0,a['flag'],a['scratch'],len(a['indices']),*a['values'],*a['ignore'],*a['origin'],*a['indices']]
8
+ def project(s):return {k:s[k] for k in ['saved','sets','file','values','ignore','origin','scan_result','cvars','ring','info_ring']}|{'load_events':s['load']['events']}
9
+
10
+ def main():
11
+ b=binary();p=Path('docs/research/qvm-persistence-roundtrip.json');data=p.read_bytes();rows=[]
12
+ for s in json.loads(data)['probes']:rows.append({'source':project(s),'rust':result(b,'roundtrip',encode(s))})
13
+ write_json(Path('docs/research/rust-persistence-roundtrip.json'),{'source_report':str(p),'source_report_sha256':hashlib.sha256(data).hexdigest(),'probes':rows,'limits':'Rust matches144 original save/session-write/restore/read compositions using owned in-memory cvar/file bytes. Partial integer parsing and caller scratch preserve the observed non-invertibility of negative/INT_MIN session text; file prefixes and ignore accumulation remain exact. Engine/destination/selector changes stage atomically on errors. This adapter does not own native handles or perform OS file IO.'})
14
+ if __name__=='__main__':main()
scripts/test_rust_persistence_roundtrip.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib,json,unittest
2
+ from pathlib import Path
3
+ class PortTests(unittest.TestCase):
4
+ def test_all_original_cases(self):
5
+ r=json.loads(Path('docs/research/rust-persistence-roundtrip.json').read_text());src=json.loads(Path(r['source_report']).read_text());self.assertEqual(len(r['probes']),len(src['probes']));self.assertGreater(len(r['probes']),20)
6
+ for p in r['probes']:self.assertEqual(p['rust'],p['source'])
7
+ def test_source_identity(self):
8
+ r=json.loads(Path('docs/research/rust-persistence-roundtrip.json').read_text());self.assertEqual(hashlib.sha256(Path(r['source_report']).read_bytes()).hexdigest(),r['source_report_sha256'])
9
+ if __name__=='__main__':unittest.main()
xtask/checks.tsv CHANGED
@@ -2868,3 +2868,18 @@ report scripts/analyze_qvm_persistence_roundtrip.py docs/research/qvm-persistenc
2868
  edge docs/research/qvm-persistence-roundtrip.json scripts/test_qvm_persistence_roundtrip.py
2869
  group qvm-persistence-roundtrip scripts/analyze_qvm_persistence_roundtrip.py
2870
  edge scripts/analyze_qvm_persistence_roundtrip.py scripts/analyze_qvm_resumer_audit.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2868
  edge docs/research/qvm-persistence-roundtrip.json scripts/test_qvm_persistence_roundtrip.py
2869
  group qvm-persistence-roundtrip scripts/analyze_qvm_persistence_roundtrip.py
2870
  edge scripts/analyze_qvm_persistence_roundtrip.py scripts/analyze_qvm_resumer_audit.py
2871
+ edge Cargo.toml scripts/analyze_rust_persistence_roundtrip.py
2872
+ edge Cargo.lock scripts/analyze_rust_persistence_roundtrip.py
2873
+ edge crates/urt-gameplay/Cargo.toml scripts/analyze_rust_persistence_roundtrip.py
2874
+ edge crates/urt-gameplay/src/lib.rs scripts/analyze_rust_persistence_roundtrip.py
2875
+ edge crates/urt-gameplay/src/native_policy.rs scripts/analyze_rust_persistence_roundtrip.py
2876
+ edge crates/urt-gameplay/src/bin/urt-native-policy.rs scripts/analyze_rust_persistence_roundtrip.py
2877
+ edge docs/research/qvm-persistence-roundtrip.json scripts/analyze_rust_persistence_roundtrip.py
2878
+ group rust-persistence-roundtrip scripts/analyze_rust_health_impulse.py
2879
+ group gameplay scripts/analyze_rust_persistence_roundtrip.py
2880
+ edge crates/urt-gameplay/src/intermission.rs scripts/analyze_rust_persistence_roundtrip.py
2881
+ edge crates/urt-gameplay/src/qvm_format.rs scripts/analyze_rust_persistence_roundtrip.py
2882
+ report scripts/analyze_rust_persistence_roundtrip.py docs/research/rust-persistence-roundtrip.json
2883
+ edge docs/research/rust-persistence-roundtrip.json scripts/test_rust_persistence_roundtrip.py
2884
+ group rust-persistence-roundtrip scripts/analyze_rust_persistence_roundtrip.py
2885
+ edge scripts/analyze_rust_persistence_roundtrip.py scripts/analyze_qvm_resumer_audit.py