nmarshall commited on
Commit
8af2522
·
1 Parent(s): 2d840a0

gameplay: port session initialization ownership

Browse files
crates/urt-gameplay/src/bin/urt-native-policy.rs CHANGED
@@ -507,6 +507,47 @@ fn run() -> Result<String, String> {
507
  .unwrap_or("null".into())
508
  ))
509
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
510
  _ => Err("unknown command".into()),
511
  }
512
  }
 
507
  .unwrap_or("null".into())
508
  ))
509
  }
510
+ "session-init" => {
511
+ if w.len() < 9 {
512
+ return Err("missing init header".into());
513
+ }
514
+ let mut p = 9;
515
+ let info = input_blob(&w, &mut p)?.ok_or("null info")?;
516
+ let mut raw = input_blob(&w, &mut p)?.ok_or("null raw")?;
517
+ if p != w.len() {
518
+ return Err("trailing init words".into());
519
+ }
520
+ let mut selector = 0;
521
+ let out = initialize_session(
522
+ &mut raw,
523
+ InitializeInput {
524
+ enabled: 1,
525
+ mapname: b"ut4_test",
526
+ file_length: 11,
527
+ file_data: b"9,8,7,6,5,4",
528
+ mode: w[0] as i32,
529
+ slot: w[1] as usize,
530
+ limit: w[2] as i32,
531
+ occupancy: w[3] as i32,
532
+ seconds: w[4] as i32,
533
+ clock: w[5] as i32,
534
+ ticks: w[6],
535
+ handle: w[7],
536
+ client_id: w[8],
537
+ userinfo: &info,
538
+ },
539
+ &mut selector,
540
+ )
541
+ .map_err(|e| format!("{e:?}"))?;
542
+ Ok(format!(
543
+ "{{\"raw\":\"{}\",\"sets\":{},\"ring\":{},\"info_ring\":{selector},\"diagnostics\":[{}],\"load_events\":{}}}",
544
+ hex(&raw),
545
+ sets_json(&out.session.sets),
546
+ out.ring_advance,
547
+ list(&out.diagnostic),
548
+ position_events(&out.load.io.events)
549
+ ))
550
+ }
551
  _ => Err("unknown command".into()),
552
  }
553
  }
crates/urt-gameplay/src/native_policy.rs CHANGED
@@ -1332,3 +1332,169 @@ mod restore_tail_tests {
1332
  assert_eq!(out.words[5], 6f32.to_bits());
1333
  }
1334
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1332
  assert_eq!(out.words[5], 6f32.to_bits());
1333
  }
1334
  }
1335
+
1336
+ pub const SESSION_BYTES: usize = 944;
1337
+ fn raw_word(raw: &[u8], offset: usize) -> u32 {
1338
+ u32::from_le_bytes(raw[offset..offset + 4].try_into().unwrap())
1339
+ }
1340
+ fn raw_store(raw: &mut [u8], offset: usize, value: u32) {
1341
+ raw[offset..offset + 4].copy_from_slice(&value.to_le_bytes());
1342
+ }
1343
+ pub struct InitializeInput<'a> {
1344
+ pub mode: i32,
1345
+ pub slot: usize,
1346
+ pub userinfo: &'a [u8],
1347
+ pub limit: i32,
1348
+ pub occupancy: i32,
1349
+ pub seconds: i32,
1350
+ pub clock: i32,
1351
+ pub ticks: u32,
1352
+ pub handle: u32,
1353
+ pub client_id: u32,
1354
+ pub enabled: i32,
1355
+ pub mapname: &'a [u8],
1356
+ pub file_length: i32,
1357
+ pub file_data: &'a [u8],
1358
+ }
1359
+ #[derive(Clone, Debug, PartialEq, Eq)]
1360
+ pub struct InitializedSession {
1361
+ pub load: PositionLoad,
1362
+ pub session: SessionWrite,
1363
+ pub diagnostic: [u32; 3],
1364
+ pub ring_advance: u32,
1365
+ }
1366
+ /// Original247520 with explicit raw-field ownership. Byte clears preserve the
1367
+ /// other three bytes; only successful position load replaces saved coordinates.
1368
+ pub fn initialize_session(
1369
+ raw: &mut [u8],
1370
+ input: InitializeInput<'_>,
1371
+ info_ring: &mut u32,
1372
+ ) -> Result<InitializedSession, Error> {
1373
+ if raw.len() != SESSION_BYTES || input.slot >= 64 {
1374
+ return Err(Error::InvalidCount);
1375
+ }
1376
+ let mut bytes = raw.to_vec();
1377
+ let mut selector = *info_ring;
1378
+ let team = if crate::intermission::team_mode(input.mode) {
1379
+ let old = raw_word(&bytes, 0);
1380
+ if old == 0 { 3 } else { old }
1381
+ } else {
1382
+ let t = info_lookup(Some(input.userinfo), Some(b"team"), &mut selector)?;
1383
+ if t.value.first() == Some(&b's') || input.limit > 0 && input.occupancy >= input.limit {
1384
+ 3
1385
+ } else {
1386
+ 0
1387
+ }
1388
+ };
1389
+ raw_store(&mut bytes, 0, team);
1390
+ raw_store(&mut bytes, 8, input.clock as u32);
1391
+ raw_store(&mut bytes, 12, 1);
1392
+ let timeout = if input.seconds != 0 {
1393
+ input.seconds.wrapping_mul(1000)
1394
+ } else {
1395
+ 60000
1396
+ };
1397
+ raw_store(&mut bytes, 44, input.clock.wrapping_add(timeout) as u32);
1398
+ for offset in [48, 52, 56, 64, 104, 492, 592, 612, 616, 644] {
1399
+ raw_store(&mut bytes, offset, 0);
1400
+ }
1401
+ raw_store(&mut bytes, 60, input.ticks);
1402
+ bytes[68..104].fill(0);
1403
+ bytes[68] = b' ';
1404
+ for offset in [108, 236, 496, 528, 560] {
1405
+ bytes[offset] = 0;
1406
+ }
1407
+ bytes[684..940].fill(0);
1408
+ let values = std::array::from_fn(|j| raw_word(&bytes, 620 + 4 * j));
1409
+ let loaded = load_position(
1410
+ PositionInput {
1411
+ mode: input.mode,
1412
+ enabled: input.enabled,
1413
+ slot: input.slot,
1414
+ userinfo: input.userinfo,
1415
+ mapname: input.mapname,
1416
+ name: b"",
1417
+ values,
1418
+ handle: input.handle,
1419
+ },
1420
+ input.file_length,
1421
+ input.file_data,
1422
+ &mut selector,
1423
+ )?;
1424
+ for (j, v) in loaded.values.iter().enumerate() {
1425
+ raw_store(&mut bytes, 620 + 4 * j, *v);
1426
+ }
1427
+ let fields = [0, 60, 48, 44, 52, 56, 612, 616, 644].map(|o| raw_word(&bytes, o) as i32);
1428
+ let session = session_write(input.slot, 0, fields, &[], &[0; 64])?;
1429
+ let ring_advance = session.ring_advance + loaded.io.ring_advance;
1430
+ raw.copy_from_slice(&bytes);
1431
+ *info_ring = selector;
1432
+ Ok(InitializedSession {
1433
+ load: loaded,
1434
+ session,
1435
+ diagnostic: [83576, input.client_id, input.ticks],
1436
+ ring_advance,
1437
+ })
1438
+ }
1439
+ #[cfg(test)]
1440
+ mod init_tests {
1441
+ use super::*;
1442
+ #[test]
1443
+ fn byte_clear_preserves_neighbor_bytes() {
1444
+ let mut raw = vec![0xdd; SESSION_BYTES];
1445
+ let mut ring = 0;
1446
+ let out = initialize_session(
1447
+ &mut raw,
1448
+ InitializeInput {
1449
+ enabled: 1,
1450
+ mapname: b"ut4_test",
1451
+ file_length: 11,
1452
+ file_data: b"9,8,7,6,5,4",
1453
+ mode: 3,
1454
+ slot: 0,
1455
+ userinfo: b"",
1456
+ limit: 0,
1457
+ occupancy: 0,
1458
+ seconds: 0,
1459
+ clock: 7,
1460
+ ticks: 9,
1461
+ handle: 0,
1462
+ client_id: 2,
1463
+ },
1464
+ &mut ring,
1465
+ )
1466
+ .unwrap();
1467
+ assert_eq!(&raw[108..112], &[0, 0xdd, 0xdd, 0xdd]);
1468
+ assert_eq!(raw_word(&raw, 44), 60007);
1469
+ assert_eq!(out.session.sets[1].1, b"");
1470
+ }
1471
+ #[test]
1472
+ fn bad_raw_domain_does_not_change_selector() {
1473
+ let mut raw = [];
1474
+ let mut ring = 1;
1475
+ assert_eq!(
1476
+ initialize_session(
1477
+ &mut raw,
1478
+ InitializeInput {
1479
+ enabled: 1,
1480
+ mapname: b"ut4_test",
1481
+ file_length: 11,
1482
+ file_data: b"9,8,7,6,5,4",
1483
+ mode: 0,
1484
+ slot: 0,
1485
+ userinfo: b"",
1486
+ limit: 0,
1487
+ occupancy: 0,
1488
+ seconds: 0,
1489
+ clock: 0,
1490
+ ticks: 0,
1491
+ handle: 0,
1492
+ client_id: 0
1493
+ },
1494
+ &mut ring
1495
+ ),
1496
+ Err(Error::InvalidCount)
1497
+ );
1498
+ assert_eq!(ring, 1);
1499
+ }
1500
+ }
docs/FINDINGS.md CHANGED
@@ -1712,3 +1712,6 @@ RE-813–822 replaces the scoreboard decimal assumption with actual37914 executi
1712
 
1713
 
1714
  **RE-904:** 96 actual247520 compositions execute team classification, userinfo lookup, bounded copy, saved-position load and session serialization. Team/default/capacity policy, wrapping timeout arithmetic, native tick snapshot, byte-only versus word clears,36-byte text initialization and64 ignored-client clears are recovered. Existing position fields persist unless the recovered load succeeds. Diagnostic7259 and engine/file operations remain captured/supplied; caller authentication and post-initialization behavior remain open. [Evidence](research/QVM_SESSION_INIT.md).
 
 
 
 
1712
 
1713
 
1714
  **RE-904:** 96 actual247520 compositions execute team classification, userinfo lookup, bounded copy, saved-position load and session serialization. Team/default/capacity policy, wrapping timeout arithmetic, native tick snapshot, byte-only versus word clears,36-byte text initialization and64 ignored-client clears are recovered. Existing position fields persist unless the recovered load succeeds. Diagnostic7259 and engine/file operations remain captured/supplied; caller authentication and post-initialization behavior remain open. [Evidence](research/QVM_SESSION_INIT.md).
1715
+
1716
+
1717
+ **RE-905:** Rust matches96 original247520 initialization/load/write cases, preserving raw byte/word clears, timeout wrapping, team/capacity selection,64 ignore clears and immediate session cvar bytes. Existing coordinates survive failed/suppressed loads. Engine clock/userinfo/files and diagnostic logging remain supplied/intended; caller behavior and native lifecycle remain open. [Evidence](research/RUST_SESSION_INIT.md).
docs/PROVISIONAL_TODO.md CHANGED
@@ -1749,3 +1749,6 @@ RE-894–903 completed bounded userinfo/scanner/saved-file/session-restore sourc
1749
 
1750
 
1751
  RE-904: 96 actual247520 compositions execute team classification, userinfo lookup, bounded copy, saved-position load and session serialization. Team/default/capacity policy, wrapping timeout arithmetic, native tick snapshot, byte-only versus word clears,36-byte text initialization and64 ignored-client clears are recovered. Existing position fields persist unless the recovered load succeeds. Diagnostic7259 and engine/file operations remain captured/supplied; caller authentication and post-initialization behavior remain open. Full runtime completion remains open. [Evidence](research/QVM_SESSION_INIT.md).
 
 
 
 
1749
 
1750
 
1751
  RE-904: 96 actual247520 compositions execute team classification, userinfo lookup, bounded copy, saved-position load and session serialization. Team/default/capacity policy, wrapping timeout arithmetic, native tick snapshot, byte-only versus word clears,36-byte text initialization and64 ignored-client clears are recovered. Existing position fields persist unless the recovered load succeeds. Diagnostic7259 and engine/file operations remain captured/supplied; caller authentication and post-initialization behavior remain open. Full runtime completion remains open. [Evidence](research/QVM_SESSION_INIT.md).
1752
+
1753
+
1754
+ RE-905: Rust matches96 original247520 initialization/load/write cases, preserving raw byte/word clears, timeout wrapping, team/capacity selection,64 ignore clears and immediate session cvar bytes. Existing coordinates survive failed/suppressed loads. Engine clock/userinfo/files and diagnostic logging remain supplied/intended; caller behavior and native lifecycle remain open. Full runtime completion remains open. [Evidence](research/RUST_SESSION_INIT.md).
docs/RESEARCH_LEDGER.md CHANGED
@@ -2240,3 +2240,8 @@ After the tenth commit, `cargo xtask test-full --since 6e01995b` passed with the
2240
  ## 2026-10-28 — RE-904 Original session initialization and immediate persistence
2241
 
2242
  96 actual247520 compositions execute team classification, userinfo lookup, bounded copy, saved-position load and session serialization. Team/default/capacity policy, wrapping timeout arithmetic, native tick snapshot, byte-only versus word clears,36-byte text initialization and64 ignored-client clears are recovered. Existing position fields persist unless the recovered load succeeds. Diagnostic7259 and engine/file operations remain captured/supplied; caller authentication and post-initialization behavior remain open. Focused `cargo xtask test-group qvm-session-init` 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.
 
 
 
 
 
 
2240
  ## 2026-10-28 — RE-904 Original session initialization and immediate persistence
2241
 
2242
  96 actual247520 compositions execute team classification, userinfo lookup, bounded copy, saved-position load and session serialization. Team/default/capacity policy, wrapping timeout arithmetic, native tick snapshot, byte-only versus word clears,36-byte text initialization and64 ignored-client clears are recovered. Existing position fields persist unless the recovered load succeeds. Diagnostic7259 and engine/file operations remain captured/supplied; caller authentication and post-initialization behavior remain open. Focused `cargo xtask test-group qvm-session-init` 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.
2243
+
2244
+
2245
+ ## 2026-10-28 — RE-905 Rust session initialization and raw-field ownership
2246
+
2247
+ Rust matches96 original247520 initialization/load/write cases, preserving raw byte/word clears, timeout wrapping, team/capacity selection,64 ignore clears and immediate session cvar bytes. Existing coordinates survive failed/suppressed loads. Engine clock/userinfo/files and diagnostic logging remain supplied/intended; caller behavior and native lifecycle remain open. Focused `cargo xtask test-group rust-session-init` 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
@@ -1786,3 +1786,6 @@ Purpose: turn the supplied installation into an evidence-backed specification of
1786
 
1787
 
1788
  - [x] RE-904 Original session initialization and immediate persistence ([evidence](research/QVM_SESSION_INIT.md)).
 
 
 
 
1786
 
1787
 
1788
  - [x] RE-904 Original session initialization and immediate persistence ([evidence](research/QVM_SESSION_INIT.md)).
1789
+
1790
+
1791
+ - [x] RE-905 Rust session initialization and raw-field ownership ([evidence](research/RUST_SESSION_INIT.md)).
docs/research/README.md CHANGED
@@ -1321,3 +1321,6 @@ The extension collector adds five focused tests for native tail calls, command-r
1321
 
1322
 
1323
  [RE-904: Original session initialization and immediate persistence](QVM_SESSION_INIT.md).
 
 
 
 
1321
 
1322
 
1323
  [RE-904: Original session initialization and immediate persistence](QVM_SESSION_INIT.md).
1324
+
1325
+
1326
+ [RE-905: Rust session initialization and raw-field ownership](RUST_SESSION_INIT.md).
docs/research/RUST_SESSION_INIT.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Rust session initialization and raw-field ownership
2
+
3
+ RE-905. Reproduce with `python3 scripts/analyze_rust_session_init.py`; focused validation: `cargo xtask test-group rust-session-init`. [Machine report](rust-session-init.json).
4
+
5
+ Rust matches96 original247520 initialization/load/write cases, preserving raw byte/word clears, timeout wrapping, team/capacity selection,64 ignore clears and immediate session cvar bytes. Existing coordinates survive failed/suppressed loads. Engine clock/userinfo/files and diagnostic logging remain supplied/intended; caller behavior and native lifecycle remain open.
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": 159,
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": 161,
2989
  "source_report": "docs/research/qvm-health-impulse.json",
2990
  "source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
2991
  }
docs/research/rust-session-init.json ADDED
The diff for this file is too large to render. See raw diff
 
scripts/analyze_rust_session_init.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 blob(s):return [len(bytes.fromhex(s)),*bytes.fromhex(s)]
7
+ def encode(s):
8
+ a=s['input'];return [a['mode'],a['slot'],a['limit'],a['occupancy'],a['seconds'],a['now'],a['ticks'],a['handle'],17+a['slot'],*blob(a['info']),*blob(a['before'])]
9
+ def project(s):return {k:s[k] for k in ['raw','ring','info_ring','diagnostics']}|{'sets':s['parts']['session']['sets'],'load_events':s['parts']['load']['events']}
10
+
11
+ def main():
12
+ b=binary();p=Path('docs/research/qvm-session-init.json');data=p.read_bytes();rows=[]
13
+ for s in json.loads(data)['probes']:rows.append({'source':project(s),'rust':result(b,'session-init',encode(s))})
14
+ write_json(Path('docs/research/rust-session-init.json'),{'source_report':str(p),'source_report_sha256':hashlib.sha256(data).hexdigest(),'probes':rows,'limits':'Rust matches96 original247520 initialization/load/write cases, preserving raw byte/word clears, timeout wrapping, team/capacity selection,64 ignore clears and immediate session cvar bytes. Existing coordinates survive failed/suppressed loads. Engine clock/userinfo/files and diagnostic logging remain supplied/intended; caller behavior and native lifecycle remain open.'})
15
+ if __name__=='__main__':main()
scripts/test_rust_session_init.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-session-init.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-session-init.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
@@ -2807,3 +2807,18 @@ report scripts/analyze_qvm_session_init.py docs/research/qvm-session-init.json
2807
  edge docs/research/qvm-session-init.json scripts/test_qvm_session_init.py
2808
  group qvm-session-init scripts/analyze_qvm_session_init.py
2809
  edge scripts/analyze_qvm_session_init.py scripts/analyze_qvm_resumer_audit.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2807
  edge docs/research/qvm-session-init.json scripts/test_qvm_session_init.py
2808
  group qvm-session-init scripts/analyze_qvm_session_init.py
2809
  edge scripts/analyze_qvm_session_init.py scripts/analyze_qvm_resumer_audit.py
2810
+ edge Cargo.toml scripts/analyze_rust_session_init.py
2811
+ edge Cargo.lock scripts/analyze_rust_session_init.py
2812
+ edge crates/urt-gameplay/Cargo.toml scripts/analyze_rust_session_init.py
2813
+ edge crates/urt-gameplay/src/lib.rs scripts/analyze_rust_session_init.py
2814
+ edge crates/urt-gameplay/src/native_policy.rs scripts/analyze_rust_session_init.py
2815
+ edge crates/urt-gameplay/src/bin/urt-native-policy.rs scripts/analyze_rust_session_init.py
2816
+ edge docs/research/qvm-session-init.json scripts/analyze_rust_session_init.py
2817
+ group rust-session-init scripts/analyze_rust_health_impulse.py
2818
+ group gameplay scripts/analyze_rust_session_init.py
2819
+ edge crates/urt-gameplay/src/intermission.rs scripts/analyze_rust_session_init.py
2820
+ edge crates/urt-gameplay/src/qvm_format.rs scripts/analyze_rust_session_init.py
2821
+ report scripts/analyze_rust_session_init.py docs/research/rust-session-init.json
2822
+ edge docs/research/rust-session-init.json scripts/test_rust_session_init.py
2823
+ group rust-session-init scripts/analyze_rust_session_init.py
2824
+ edge scripts/analyze_rust_session_init.py scripts/analyze_qvm_resumer_audit.py