nmarshall commited on
Commit
6e01995
·
1 Parent(s): 37371f3

gameplay: integrate normal map session persistence

Browse files
crates/urt-gameplay/src/bin/urt-native-policy.rs CHANGED
@@ -231,6 +231,116 @@ fn run() -> Result<String, String> {
231
  .map_err(|e| format!("{e:?}"))?;
232
  Ok(post_session_json(&out, &clients))
233
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  _ => Err("unknown command".into()),
235
  }
236
  }
@@ -329,3 +439,69 @@ fn post_session_json(out: &PostSession, c: &[SessionClient]) -> String {
329
  out.ring_advance
330
  )
331
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  .map_err(|e| format!("{e:?}"))?;
232
  Ok(post_session_json(&out, &clients))
233
  }
234
+ "normal-session" => {
235
+ if w.len() != 256 || w[15] != 8 {
236
+ return Err("invalid normal session input".into());
237
+ }
238
+ let mut map = map_state(&w[..11]);
239
+ let mut rotation = urt_gameplay::map_commands::Rotation {
240
+ enabled: 1,
241
+ cursor: 1,
242
+ limit: 2,
243
+ filename: b"previous.cfg".to_vec(),
244
+ };
245
+ let mut mirror = if w[14] != 0 {
246
+ b"cycle".to_vec()
247
+ } else {
248
+ vec![]
249
+ };
250
+ let mut clients = w[16..56]
251
+ .chunks_exact(5)
252
+ .map(|v| urt_gameplay::transition::MapClient {
253
+ score: v[0] as i32,
254
+ field304: v[1] as i32,
255
+ field308: v[2] as i32,
256
+ connection: v[3] as i32,
257
+ class: v[4] as i32,
258
+ })
259
+ .collect::<Vec<_>>();
260
+ let flags = std::array::from_fn(|j| w[192 + j] as i32);
261
+ let mut sessions = w[56..128]
262
+ .chunks_exact(9)
263
+ .map(|v| SessionClient {
264
+ connection: 0,
265
+ values: std::array::from_fn(|j| v[j] as i32),
266
+ ignore: flags,
267
+ })
268
+ .collect::<Vec<_>>();
269
+ let indices = w[128..192].iter().map(|v| *v as usize).collect::<Vec<_>>();
270
+ let out = normal_map_session(
271
+ NormalSessionState {
272
+ map: &mut map,
273
+ rotation: &mut rotation,
274
+ mirror: &mut mirror,
275
+ clients: &mut clients,
276
+ sessions: &mut sessions,
277
+ },
278
+ NormalSessionInput {
279
+ maximum: w[11] as usize,
280
+ level_count: w[12] as i32,
281
+ updated_filename: b"",
282
+ engine_map: b"current_map",
283
+ entity_clients: &indices,
284
+ },
285
+ |j, c| {
286
+ apply_session_change(w[13], j, c);
287
+ Ok(())
288
+ },
289
+ )
290
+ .map_err(|e| format!("{e:?}"))?;
291
+ let after = clients
292
+ .iter()
293
+ .map(|c| {
294
+ vec![
295
+ c.score as u32,
296
+ c.field304 as u32,
297
+ c.field308 as u32,
298
+ c.connection as u32,
299
+ c.class as u32,
300
+ ]
301
+ })
302
+ .collect::<Vec<_>>();
303
+ let values = sessions
304
+ .iter()
305
+ .map(|c| c.values.map(|v| v as u32).to_vec())
306
+ .collect::<Vec<_>>();
307
+ let calls = out
308
+ .persistence
309
+ .calls
310
+ .iter()
311
+ .map(|(k, j, v)| {
312
+ vec![
313
+ if *k == PostMapChild::First {
314
+ 246746
315
+ } else {
316
+ 246963
317
+ },
318
+ *j as u32,
319
+ *v as u32,
320
+ ]
321
+ })
322
+ .collect::<Vec<_>>();
323
+ let command = out
324
+ .normal
325
+ .selection
326
+ .command
327
+ .as_ref()
328
+ .map(|v| format!("\"{}\"", hex(v)))
329
+ .unwrap_or("null".into());
330
+ Ok(format!(
331
+ "{{\"state\":{},\"clients_after\":{},\"values\":{},\"ring\":{},\"mirror\":\"{}\",\"middle\":{},\"post_calls\":{},\"sets\":{},\"command\":{},\"rotation_ops\":{}}}",
332
+ map_json(&map),
333
+ nested(&after),
334
+ nested(&values),
335
+ u32::from(w[14] != 0) + out.persistence.ring_advance,
336
+ hex(&mirror),
337
+ clients_json(&out.normal.middle[..4]),
338
+ nested(&calls),
339
+ sets_json(&out.persistence.sets),
340
+ command,
341
+ rotation_ops(&out.normal.rotation)
342
+ ))
343
+ }
344
  _ => Err("unknown command".into()),
345
  }
346
  }
 
439
  out.ring_advance
440
  )
441
  }
442
+
443
+ fn map_state(v: &[u32]) -> urt_gameplay::transition::MapState {
444
+ urt_gameplay::transition::MapState {
445
+ phase: v[0] as i32,
446
+ option: v[1] as i32,
447
+ flags: v[2],
448
+ mode: v[3] as i32,
449
+ scores: [v[4] as i32, v[5] as i32],
450
+ blocked: v[6] as i32,
451
+ pending: v[7] as i32,
452
+ saved: [v[8] as i32, v[9] as i32],
453
+ round: v[10] as i32,
454
+ }
455
+ }
456
+ fn map_json(s: &urt_gameplay::transition::MapState) -> String {
457
+ let keys = [
458
+ "phase",
459
+ "option",
460
+ "flags",
461
+ "mode",
462
+ "first",
463
+ "second",
464
+ "blocked",
465
+ "pending",
466
+ "saved_second",
467
+ "saved_first",
468
+ "round",
469
+ ];
470
+ let values = [
471
+ s.phase as u32,
472
+ s.option as u32,
473
+ s.flags,
474
+ s.mode as u32,
475
+ s.scores[0] as u32,
476
+ s.scores[1] as u32,
477
+ s.blocked as u32,
478
+ s.pending as u32,
479
+ s.saved[0] as u32,
480
+ s.saved[1] as u32,
481
+ s.round as u32,
482
+ ];
483
+ format!(
484
+ "{{{}}}",
485
+ keys.iter()
486
+ .zip(values)
487
+ .map(|(k, v)| format!("\"{k}\":{v}"))
488
+ .collect::<Vec<_>>()
489
+ .join(",")
490
+ )
491
+ }
492
+ fn clients_json(c: &[urt_gameplay::transition::MapClient]) -> String {
493
+ format!(
494
+ "[{}]",
495
+ c.iter()
496
+ .map(|c| format!(
497
+ "{{\"248\":{},\"304\":{},\"308\":{},\"468\":{},\"696\":{}}}",
498
+ c.score as u32,
499
+ c.field304 as u32,
500
+ c.field308 as u32,
501
+ c.connection as u32,
502
+ c.class as u32
503
+ ))
504
+ .collect::<Vec<_>>()
505
+ .join(",")
506
+ )
507
+ }
crates/urt-gameplay/src/native_policy.rs CHANGED
@@ -550,3 +550,144 @@ mod post_session_tests {
550
  assert_eq!(c, before);
551
  }
552
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
550
  assert_eq!(c, before);
551
  }
552
  }
553
+
554
+ pub struct NormalSessionInput<'a> {
555
+ pub maximum: usize,
556
+ pub level_count: i32,
557
+ pub updated_filename: &'a [u8],
558
+ pub engine_map: &'a [u8],
559
+ pub entity_clients: &'a [usize],
560
+ }
561
+ pub struct NormalSessionState<'a> {
562
+ pub map: &'a mut crate::transition::MapState,
563
+ pub rotation: &'a mut crate::map_commands::Rotation,
564
+ pub mirror: &'a mut Vec<u8>,
565
+ pub clients: &'a mut [crate::transition::MapClient],
566
+ pub sessions: &'a mut [SessionClient],
567
+ }
568
+ #[derive(Clone, Debug, PartialEq, Eq)]
569
+ pub struct NormalSession {
570
+ pub normal: crate::map_commands::NormalFlow,
571
+ pub persistence: PostSession,
572
+ }
573
+ /// Stage normal map intentions, score reset, live-level persistence dispatch,
574
+ /// and the final max-client connection scan. First-child session effects are
575
+ /// supplied; external engine actions remain returned intentions.
576
+ pub fn normal_map_session<F: FnMut(usize, &mut [SessionClient]) -> Result<(), Error>>(
577
+ state: NormalSessionState<'_>,
578
+ input: NormalSessionInput<'_>,
579
+ first: F,
580
+ ) -> Result<NormalSession, Error> {
581
+ if state.map.phase > 0 && crate::intermission::team_mode(state.map.mode) {
582
+ return Err(Error::InvalidInput);
583
+ }
584
+ if state.clients.len() > 64
585
+ || state.clients.len() != state.sessions.len()
586
+ || input.maximum > state.clients.len()
587
+ {
588
+ return Err(Error::InvalidCount);
589
+ }
590
+ let mut map = state.map.clone();
591
+ let mut rotation = state.rotation.clone();
592
+ let mut clients = state.clients.to_vec();
593
+ let mut sessions = state.sessions.to_vec();
594
+ let operations = crate::map_commands::advance_rotation(&mut rotation, input.updated_filename)
595
+ .map_err(|_| Error::InvalidInput)?;
596
+ let selection = crate::map_commands::select_map(state.mirror, input.engine_map)
597
+ .map_err(|_| Error::InvalidInput)?;
598
+ crate::transition::prepare_normal(&mut map, &mut clients, input.maximum)
599
+ .map_err(|_| Error::InvalidCount)?;
600
+ let middle = clients.clone();
601
+ for (s, c) in sessions.iter_mut().zip(&clients) {
602
+ s.connection = c.connection;
603
+ }
604
+ let persistence = persist_post_map(
605
+ &mut sessions,
606
+ input.level_count,
607
+ input.entity_clients,
608
+ first,
609
+ )?;
610
+ for (s, c) in sessions.iter().zip(&mut clients) {
611
+ c.connection = s.connection;
612
+ }
613
+ crate::transition::mark_connecting(&mut clients, input.maximum)
614
+ .map_err(|_| Error::InvalidCount)?;
615
+ for (s, c) in sessions.iter_mut().zip(&clients) {
616
+ s.connection = c.connection;
617
+ }
618
+ *state.map = map;
619
+ *state.rotation = rotation;
620
+ *state.mirror = selection.mirror.clone();
621
+ state.clients.copy_from_slice(&clients);
622
+ state.sessions.clone_from_slice(&sessions);
623
+ Ok(NormalSession {
624
+ normal: crate::map_commands::NormalFlow {
625
+ rotation: operations,
626
+ selection,
627
+ middle,
628
+ },
629
+ persistence,
630
+ })
631
+ }
632
+ #[cfg(test)]
633
+ mod normal_session_tests {
634
+ use super::*;
635
+ use crate::{
636
+ map_commands::Rotation,
637
+ transition::{MapClient, MapState},
638
+ };
639
+ #[test]
640
+ fn bad_level_count_preserves_all_state() {
641
+ let mut map = MapState {
642
+ phase: 2,
643
+ mode: 9,
644
+ ..Default::default()
645
+ };
646
+ let before = map.clone();
647
+ let mut rotation = Rotation {
648
+ enabled: 1,
649
+ cursor: 1,
650
+ limit: 2,
651
+ filename: b"old".to_vec(),
652
+ };
653
+ let old = rotation.clone();
654
+ let mut mirror = b"cycle".to_vec();
655
+ let mut clients = [MapClient {
656
+ score: 7,
657
+ field304: 8,
658
+ field308: 9,
659
+ connection: 2,
660
+ class: 1,
661
+ }];
662
+ let c = clients;
663
+ let mut sessions = [SessionClient {
664
+ connection: 2,
665
+ values: [0; 9],
666
+ ignore: [0; 64],
667
+ }];
668
+ let s = sessions.clone();
669
+ let result = normal_map_session(
670
+ NormalSessionState {
671
+ map: &mut map,
672
+ rotation: &mut rotation,
673
+ mirror: &mut mirror,
674
+ clients: &mut clients,
675
+ sessions: &mut sessions,
676
+ },
677
+ NormalSessionInput {
678
+ maximum: 1,
679
+ level_count: 2,
680
+ updated_filename: b"new",
681
+ engine_map: b"current",
682
+ entity_clients: &[0, 0],
683
+ },
684
+ |_, _| Ok(()),
685
+ );
686
+ assert_eq!(result, Err(Error::InvalidCount));
687
+ assert_eq!(map, before);
688
+ assert_eq!(rotation, old);
689
+ assert_eq!(mirror, b"cycle");
690
+ assert_eq!(clients, c);
691
+ assert_eq!(sessions, s);
692
+ }
693
+ }
docs/FINDINGS.md CHANGED
@@ -1676,3 +1676,6 @@ RE-813–822 replaces the scoreboard decimal assumption with actual37914 executi
1676
 
1677
 
1678
  **RE-892:** 64 actual6939 normal-path compositions execute122230/7188/249007/246963 and recovered formatter/string bodies on shared state. Distinct max-clients and level counts prove reset, persistence dispatch and final state2-to1 scan use their own bounds. First-child disconnect/activation/session-field effects are supplied and observed before persistence and final scan. Bot children, map-list fallback221662, first client child246746 and engine behavior remain explicit boundaries. [Evidence](research/QVM_NORMAL_MAP_SESSION.md).
 
 
 
 
1676
 
1677
 
1678
  **RE-892:** 64 actual6939 normal-path compositions execute122230/7188/249007/246963 and recovered formatter/string bodies on shared state. Distinct max-clients and level counts prove reset, persistence dispatch and final state2-to1 scan use their own bounds. First-child disconnect/activation/session-field effects are supplied and observed before persistence and final scan. Bot children, map-list fallback221662, first client child246746 and engine behavior remain explicit boundaries. [Evidence](research/QVM_NORMAL_MAP_SESSION.md).
1679
+
1680
+
1681
+ **RE-893:** Rust matches64 continuous original6939/249007/246963 compositions. Independent max-clients/live-level bounds preserve score-reset, session-write and final connection-scan ordering; supplied first-child field/connection changes are staged with global/cvar intentions. Invalid counts reject without partial state commits. Native first child, bot/map-list callbacks and engine persistence remain open. [Evidence](research/RUST_NORMAL_MAP_SESSION.md).
docs/PROVISIONAL_TODO.md CHANGED
@@ -1707,3 +1707,9 @@ RE-891: Rust matches64 original249007/246963 compositions, including first-child
1707
 
1708
 
1709
  RE-892: 64 actual6939 normal-path compositions execute122230/7188/249007/246963 and recovered formatter/string bodies on shared state. Distinct max-clients and level counts prove reset, persistence dispatch and final state2-to1 scan use their own bounds. First-child disconnect/activation/session-field effects are supplied and observed before persistence and final scan. Bot children, map-list fallback221662, first client child246746 and engine behavior remain explicit boundaries. Full runtime completion remains open. [Evidence](research/QVM_NORMAL_MAP_SESSION.md).
 
 
 
 
 
 
 
1707
 
1708
 
1709
  RE-892: 64 actual6939 normal-path compositions execute122230/7188/249007/246963 and recovered formatter/string bodies on shared state. Distinct max-clients and level counts prove reset, persistence dispatch and final state2-to1 scan use their own bounds. First-child disconnect/activation/session-field effects are supplied and observed before persistence and final scan. Bot children, map-list fallback221662, first client child246746 and engine behavior remain explicit boundaries. Full runtime completion remains open. [Evidence](research/QVM_NORMAL_MAP_SESSION.md).
1710
+
1711
+
1712
+ RE-893: Rust matches64 continuous original6939/249007/246963 compositions. Independent max-clients/live-level bounds preserve score-reset, session-write and final connection-scan ordering; supplied first-child field/connection changes are staged with global/cvar intentions. Invalid counts reject without partial state commits. Native first child, bot/map-list callbacks and engine persistence remain open. Full runtime completion remains open. [Evidence](research/RUST_NORMAL_MAP_SESSION.md).
1713
+
1714
+
1715
+ RE-884–893 completed bounded queried spawn/camera and map persistence compositions. RE-894 tracks saved-position behavior, restore, native collision and full runtime integration. [Batch and remaining tasks](research/QVM_QUERIED_PERSISTENCE_BATCH.md).
docs/RESEARCH_LEDGER.md CHANGED
@@ -2170,3 +2170,13 @@ Rust matches64 original249007/246963 compositions, including first-child disconn
2170
  ## 2026-10-28 — RE-892 Original normal map flow with session persistence
2171
 
2172
  64 actual6939 normal-path compositions execute122230/7188/249007/246963 and recovered formatter/string bodies on shared state. Distinct max-clients and level counts prove reset, persistence dispatch and final state2-to1 scan use their own bounds. First-child disconnect/activation/session-field effects are supplied and observed before persistence and final scan. Bot children, map-list fallback221662, first client child246746 and engine behavior remain explicit boundaries. Focused `cargo xtask test-group qvm-normal-map-session` passes (8 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.
 
 
 
 
 
 
 
 
 
 
 
2170
  ## 2026-10-28 — RE-892 Original normal map flow with session persistence
2171
 
2172
  64 actual6939 normal-path compositions execute122230/7188/249007/246963 and recovered formatter/string bodies on shared state. Distinct max-clients and level counts prove reset, persistence dispatch and final state2-to1 scan use their own bounds. First-child disconnect/activation/session-field effects are supplied and observed before persistence and final scan. Bot children, map-list fallback221662, first client child246746 and engine behavior remain explicit boundaries. Focused `cargo xtask test-group qvm-normal-map-session` passes (8 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.
2173
+
2174
+
2175
+ ## 2026-10-28 — RE-893 Rust normal map transition with session persistence
2176
+
2177
+ Rust matches64 continuous original6939/249007/246963 compositions. Independent max-clients/live-level bounds preserve score-reset, session-write and final connection-scan ordering; supplied first-child field/connection changes are staged with global/cvar intentions. Invalid counts reject without partial state commits. Native first child, bot/map-list callbacks and engine persistence remain open. Focused `cargo xtask test-group rust-normal-map-session` 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.
2178
+
2179
+
2180
+ ### RE-884–893 full batch validation (2026-10-28)
2181
+
2182
+ After the tenth commit, `cargo xtask test-full --since b9a94452` passed with the flake-declared Nix Python: 1,738 Python tests,188 Rust tests (assets10, gameplay148, xtask30),33 affected report producers regenerated twice deterministically, storage policy,458 documentation link checks and whitespace guards. No generated-report drift. Ten task commits sinceb9a94452; results are amended into the tenth commit. [Findings and next work](research/QVM_QUERIED_PERSISTENCE_BATCH.md).
docs/REVERSE_ENGINEERING_TODO.md CHANGED
@@ -1716,7 +1716,7 @@ Purpose: turn the supplied installation into an evidence-backed specification of
1716
  - [x] RE-883 Rust composed bot rotation and callback ownership ([evidence](research/RUST_BOT_ROTATION.md)).
1717
 
1718
 
1719
- - [ ] RE-884 Integrate recovered overlap and post-map/bot callbacks into complete map/camera flows, confirm native botlib behavior, and validate actual Linux/macOS gameplay ([next tasks](research/QVM_BOT_COLLISION_BATCH.md)).
1720
 
1721
 
1722
  - [x] RE-884 Original fallback spawn with actual overlap queries ([evidence](research/QVM_QUERIED_SPAWN.md)).
@@ -1744,3 +1744,9 @@ Purpose: turn the supplied installation into an evidence-backed specification of
1744
 
1745
 
1746
  - [x] RE-892 Original normal map flow with session persistence ([evidence](research/QVM_NORMAL_MAP_SESSION.md)).
 
 
 
 
 
 
 
1716
  - [x] RE-883 Rust composed bot rotation and callback ownership ([evidence](research/RUST_BOT_ROTATION.md)).
1717
 
1718
 
1719
+ - [ ] Follow-up after RE-883: integrate recovered overlap and post-map/bot callbacks into complete map/camera flows, confirm native botlib behavior, and validate actual Linux/macOS gameplay ([next tasks](research/QVM_BOT_COLLISION_BATCH.md)).
1720
 
1721
 
1722
  - [x] RE-884 Original fallback spawn with actual overlap queries ([evidence](research/QVM_QUERIED_SPAWN.md)).
 
1744
 
1745
 
1746
  - [x] RE-892 Original normal map flow with session persistence ([evidence](research/QVM_NORMAL_MAP_SESSION.md)).
1747
+
1748
+
1749
+ - [x] RE-893 Rust normal map transition with session persistence ([evidence](research/RUST_NORMAL_MAP_SESSION.md)).
1750
+
1751
+
1752
+ - [ ] RE-894 Recover saved-position/session restore/native collision and complete map/intermission/runtime ownership ([next tasks](research/QVM_QUERIED_PERSISTENCE_BATCH.md)).
docs/TESTING.md CHANGED
@@ -160,3 +160,6 @@ Source/Rust pairs: `spawn-fallback`/`rust-spawn-fallback`, `camera-spawn`/`rust-
160
 
161
 
162
  RE-874–883 adds focused groups `qvm-bot-best`, `rust-bot-best`, `qvm-bot-breed`, `rust-bot-breed`, `qvm-post-map-clients`, `rust-post-map-clients`, `qvm-spawn-overlap`, `rust-spawn-overlap`, `qvm-bot-rotation` and `rust-bot-rotation`. Invoke one group per `cargo xtask test-group <name>`. Source/Rust report identity and native_policy dependencies are covered by the registry regression. Full validation at the tenth-commit boundary uses `cargo xtask test-full --since 40af75ff` in the flake tool environment with explicit Nix-store PYTHON.
 
 
 
 
160
 
161
 
162
  RE-874–883 adds focused groups `qvm-bot-best`, `rust-bot-best`, `qvm-bot-breed`, `rust-bot-breed`, `qvm-post-map-clients`, `rust-post-map-clients`, `qvm-spawn-overlap`, `rust-spawn-overlap`, `qvm-bot-rotation` and `rust-bot-rotation`. Invoke one group per `cargo xtask test-group <name>`. Source/Rust report identity and native_policy dependencies are covered by the registry regression. Full validation at the tenth-commit boundary uses `cargo xtask test-full --since 40af75ff` in the flake tool environment with explicit Nix-store PYTHON.
163
+
164
+
165
+ RE-884–893 adds focused groups `qvm-queried-spawn`, `rust-queried-spawn`, `qvm-queried-camera`, `rust-queried-camera`, `qvm-session-write`, `rust-session-write`, `qvm-post-map-session`, `rust-post-map-session`, `qvm-normal-map-session` and `rust-normal-map-session`. Invoke one group per `cargo xtask test-group <name>`. Full batch validation after task ten uses `cargo xtask test-full --since b9a94452` with explicit flake-declared Nix PYTHON. [Evidence and limits](research/QVM_QUERIED_PERSISTENCE_BATCH.md).
docs/research/QVM_BOT_COLLISION_BATCH.md CHANGED
@@ -25,7 +25,7 @@ The shared-state rotation fixtures execute122230,122143,121987 and36038. Enabled
25
 
26
  Post-map249007 iterates level count34992524, distinct from the max-clients cvar used in other paths. Each state2 client dispatches246746 followed by246963. Its second call remains unconditional even if the first disconnects that client. Supplied activation of later clients changes later dispatch. Rust supports this ordering through typed callbacks; callback changes to the count/base pointer, the two child bodies and engine integration remain open.
27
 
28
- Spawn167638 adds six original f32 bounds constants to origin fields92/96/100, then requests native -33 with capacity1024. Any returned entity with nonzero pointer at offset520 causes an immediate true result. No health/team/activity check occurs in this body. Fixtures include repeated indices, negative/zero counts, signed nonzero client words, f32 rounding and a blocking client in the last of1024 results. Actual spatial query answers remain supplied. Rust validates finite bounds and positive result counts/indices before scanning; malformed native inputs are outside the source parity domain. This query is not yet integrated into fallback/camera/world collision.
29
 
30
  ## Validation and storage
31
 
@@ -33,7 +33,7 @@ Each task used focused `cargo xtask test-group <name>` before its own commit, in
33
 
34
  All Python processes use the already-resolved Nix Python declared by flake.nix, with explicit PYTHON inherited by xtask and Git hooks. Original assets remain in .cache; no asset payload was changed. After commit ten, `cargo xtask test-full --since 40af75ff` passed once: 1,723 Python tests and180 Rust tests (assets10, gameplay140, xtask30). All64 affected report producers regenerated twice deterministically. Storage policy, links across447 documentation files and whitespace checks passed, with no generated report drift. These validation notes were amended into task ten.
35
 
36
- ## Next work: RE-884
37
 
38
  - [ ] Integrate actual167638 overlap into actual168114 fallback and6717 camera fixtures; preserve query order, short-circuiting and query-result ownership.
39
  - [ ] Connect Rust overlap queries to fallback selection through a typed spatial-query interface; then validate actual native broadphase and world collision.
@@ -43,3 +43,5 @@ All Python processes use the already-resolved Nix Python declared by flake.nix,
43
  - [ ] Reuse existing allocator277165/initializer277125 research when composing event construction, linking and lifetime; expand unsupported capacity/alias domains instead of duplicating existing work.
44
  - [ ] Compose camera/respawn175123/slow setup156523 into complete begin-intermission, and join ranking, scoreboard publication and real network/configstrings.
45
  - [ ] Validate Linux/macOS native gameplay and Bevy/Solari rendering. Full playable-game completion remains open.
 
 
 
25
 
26
  Post-map249007 iterates level count34992524, distinct from the max-clients cvar used in other paths. Each state2 client dispatches246746 followed by246963. Its second call remains unconditional even if the first disconnects that client. Supplied activation of later clients changes later dispatch. Rust supports this ordering through typed callbacks; callback changes to the count/base pointer, the two child bodies and engine integration remain open.
27
 
28
+ Spawn167638 adds six original f32 bounds constants to origin fields92/96/100, then requests native -33 with capacity1024. Any returned entity with nonzero pointer at offset520 causes an immediate true result. No health/team/activity check occurs in this body. Fixtures include repeated indices, negative/zero counts, signed nonzero client words, f32 rounding and a blocking client in the last of1024 results. Actual spatial query answers remain supplied. Rust validates finite bounds and positive result counts/indices before scanning; malformed native inputs are outside the source parity domain. The next batch integrates this query into fallback and camera fixtures; native world collision remains open.
29
 
30
  ## Validation and storage
31
 
 
33
 
34
  All Python processes use the already-resolved Nix Python declared by flake.nix, with explicit PYTHON inherited by xtask and Git hooks. Original assets remain in .cache; no asset payload was changed. After commit ten, `cargo xtask test-full --since 40af75ff` passed once: 1,723 Python tests and180 Rust tests (assets10, gameplay140, xtask30). All64 affected report producers regenerated twice deterministically. Storage policy, links across447 documentation files and whitespace checks passed, with no generated report drift. These validation notes were amended into task ten.
35
 
36
+ ## Follow-up work (expanded by RE-884–893)
37
 
38
  - [ ] Integrate actual167638 overlap into actual168114 fallback and6717 camera fixtures; preserve query order, short-circuiting and query-result ownership.
39
  - [ ] Connect Rust overlap queries to fallback selection through a typed spatial-query interface; then validate actual native broadphase and world collision.
 
43
  - [ ] Reuse existing allocator277165/initializer277125 research when composing event construction, linking and lifetime; expand unsupported capacity/alias domains instead of duplicating existing work.
44
  - [ ] Compose camera/respawn175123/slow setup156523 into complete begin-intermission, and join ranking, scoreboard publication and real network/configstrings.
45
  - [ ] Validate Linux/macOS native gameplay and Bevy/Solari rendering. Full playable-game completion remains open.
46
+
47
+ [RE-884–893](QVM_QUERIED_PERSISTENCE_BATCH.md) completes the bounded fallback/camera and normal persistence compositions. Its RE-894 list supersedes the historical follow-up list above.
docs/research/QVM_QUERIED_PERSISTENCE_BATCH.md ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Queried spawn/camera and map persistence: RE-884–893
2
+
3
+ Ten separately committed tasks compose recovered QVM routines and their Rust policies across collision and session boundaries. The five source fixture sets contain380 cases, each compared against Rust. Native collision queries, math, engine cvars and the first post-map client child remain explicit supplied/captured boundaries. This batch does not establish a complete playable game.
4
+
5
+ | Task | Evidence | Cases |
6
+ | --- | --- | ---: |
7
+ | RE-884 | [Actual fallback168114 with167638 queries](QVM_QUERIED_SPAWN.md) | 64 |
8
+ | RE-885 | [Rust queried fallback](RUST_QUERIED_SPAWN.md) | 64 |
9
+ | RE-886 | [Actual camera6717/fallback/placement6575](QVM_QUERIED_CAMERA.md) | 128 |
10
+ | RE-887 | [Rust lazy queried camera](RUST_QUERIED_CAMERA.md) | 128 |
11
+ | RE-888 | [Session246963 and bounded ignore-list serialization](QVM_SESSION_WRITE.md) | 60 |
12
+ | RE-889 | [Rust exact session bytes](RUST_SESSION_WRITE.md) | 60 |
13
+ | RE-890 | [Post-map249007 with actual persistence](QVM_POST_MAP_SESSION.md) | 64 |
14
+ | RE-891 | [Rust post-map persistence](RUST_POST_MAP_SESSION.md) | 64 |
15
+ | RE-892 | [Normal6939 with post-map persistence](QVM_NORMAL_MAP_SESSION.md) | 64 |
16
+ | RE-893 | [Rust normal map/session composition](RUST_NORMAL_MAP_SESSION.md) | 64 |
17
+
18
+ ## Collision and camera ownership
19
+
20
+ Actual168114 now invokes actual167638 rather than receiving precomputed blocked booleans. Each matching active class requests a box query before its distance calculation. Empty and nonclient responses allow ranking; a nonzero client pointer blocks the candidate. Duplicate results, all-blocked fallback, no class, the64-candidate cap, RNG state and exact output words are checked. Supplied native -33 lists include entity900 (no client) and901 (nonzero client); these are explicit engine fixtures, not a spatial index implementation.
21
+
22
+ The Rust query callback follows that same per-candidate boundary instead of eagerly querying every world entity. Filtered classes issue no queries, and callback/domain errors preserve RNG. Existing callers with booleans retain their original API and fixtures. Query side effects outside the returned/staged state cannot be rolled back; the typed subset borrows a stable world and supplied client-word slice.
23
+
24
+ Continuous6717/168114/167638/6575 fixtures verify setup, fallback selection and player placement. Primary intermission cameras bypass spatial queries completely; fallback cameras query only their matching spawn classes. Rust preserves query order, checked math transcripts, RNG, views, target selection and both player slots. Native broadphase/math, respawn and the complete begin-intermission flow remain open.
25
+
26
+ ## Session bytes and callback timing
27
+
28
+ Original246963 writes sessionN with source formatter70458/37914. The raw integer fields are offsets3096,3156,3144,3140 and3148; offsets3152,3708,3712 and3740 normalize to0/1. Names are descriptive session metadata until their producers/restore semantics are verified. Rust uses recovered decimal behavior, including the original nonstandard INT_MIN bytes, rather than host formatting.
29
+
30
+ sessIgnoreN traverses entity records, derives client indices from pointers at offset520 using stride6336, and reads the target client's table at3780+4*index. Only flag exactly1 is appended as `index,`; duplicates remain. Iteration stops at the level count or64, whichever comes first; negative counts scan nothing. Actual70031/36038/69338 concatenate into128 bytes including NUL, saturating at127 data bytes, even in the middle of a decimal token. Formatter-ring advancement continues after saturation. Native memset/strncpy are modeled, while formatter/string QVM bodies execute and engine cvar writes are captured.
31
+
32
+ The fixture initializer reads the packed original string region without padding individual literals into adjacent strings. The independent byte oracle caught an initial synthetic padding overlap before commit; the corrected source image preserves the original neighboring format strings. This is fixture initialization, not a patch to the game binary.
33
+
34
+ Post-map249007 executes actual246963 after a supplied246746 callback. A disconnect in the first child leaves the second child unconditional. Activation of later clients affects dispatch, and changed session metadata is serialized immediately. Source formatter state carries between clients. Rust stages callback/session state on errors and returns ordered cvar intentions. It holds the client count/base stable; arbitrary native aliasing and the actual first child remain outside this contract.
35
+
36
+ The normal6939 composition separates max-client bounds from the live level count. Score/global reset occurs before post-map persistence; the final max-client state2-to1 scan occurs afterwards. Tests independently vary both counts and verify callback disconnect/activation, metadata updates, mode9 resets, map-command bytes, VM mirror preservation and formatter-ring carry. Rust stages map, rotation, mirror, client and session state together. Engine command/cvar dispatch remains represented by intentions; bot callback internals and221662 map-list selection are still omitted here.
37
+
38
+ ## Validation
39
+
40
+ Focused `cargo xtask test-group <name>` ran before each task commit, with deterministic double report regeneration, relevant source/Rust parity, scoped gameplay tests and storage/documentation/whitespace guards. The ten groups are qvm-queried-spawn, rust-queried-spawn, qvm-queried-camera, rust-queried-camera, qvm-session-write, rust-session-write, qvm-post-map-session, rust-post-map-session, qvm-normal-map-session and rust-normal-map-session. Existing spawn/camera and normal-map callers received focused regression checks when shared helpers changed.
41
+
42
+ Dependency-selection regression coverage includes all five source/Rust report pairs, camera/native-policy consumers, and the transition, formatting and map-command dependencies of normal session composition. Python uses only the resolved Nix runtime declared by flake.nix, with explicit PYTHON for xtask and Git hooks. No original asset payload changed. After commit ten, `cargo xtask test-full --since b9a94452` passed once: 1,738 Python tests and188 Rust tests (assets10, gameplay148, xtask30). All33 affected report producers regenerated twice deterministically. Storage policy, links across458 documentation files and whitespace checks passed with no generated-report drift. These notes were amended into task ten.
43
+
44
+ ## Next work: RE-894
45
+
46
+ - [ ] Recover246746 saved-position behavior, actual userinfo70503 lookup, GUID/map path generation, floating formatter70386, native file write/close and ownership/error branches.
47
+ - [ ] Recover session restore and clear246938 callers; trace each metadata producer and ignore-table mutation, validating persistence across an actual map reload.
48
+ - [ ] Support and verify callback changes to client count/base, entity-client aliases, malformed native query results and collision query side effects across selection.
49
+ - [ ] Replace supplied -33 answers with recovered native broadphase/world collision; compare actual native sqrt/atan2 and connect scene lifecycle to the typed query interface.
50
+ - [ ] Integrate recovered bot rotation with the new persistence path, and recover native botlib selection/write/mutation behavior.
51
+ - [ ] Recover221662 map-list parsing and compose it with7188, normal/series6939 and readiness, including real engine cvar refresh and command dispatch.
52
+ - [ ] Compose respawn175123/slow setup156523 and complete begin-intermission, plus allocator/event lifetime and real ranking/scoreboard/network integration.
53
+ - [ ] Validate actual Linux/macOS Rust/Bevy/Solari gameplay and rendering. Cross-platform game completion remains open.
docs/research/README.md CHANGED
@@ -1279,3 +1279,9 @@ The extension collector adds five focused tests for native tail calls, command-r
1279
 
1280
 
1281
  [RE-892: Original normal map flow with session persistence](QVM_NORMAL_MAP_SESSION.md).
 
 
 
 
 
 
 
1279
 
1280
 
1281
  [RE-892: Original normal map flow with session persistence](QVM_NORMAL_MAP_SESSION.md).
1282
+
1283
+
1284
+ [RE-893: Rust normal map transition with session persistence](RUST_NORMAL_MAP_SESSION.md).
1285
+
1286
+
1287
+ [RE-884–893 queried spawn/camera and persistence batch](QVM_QUERIED_PERSISTENCE_BATCH.md).
docs/research/RUST_NORMAL_MAP_SESSION.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Rust normal map transition with session persistence
2
+
3
+ RE-893. Reproduce with `python3 scripts/analyze_rust_normal_map_session.py`; focused validation: `cargo xtask test-group rust-normal-map-session`. [Machine report](rust-normal-map-session.json).
4
+
5
+ Rust matches64 continuous original6939/249007/246963 compositions. Independent max-clients/live-level bounds preserve score-reset, session-write and final connection-scan ordering; supplied first-child field/connection changes are staged with global/cvar intentions. Invalid counts reject without partial state commits. Native first child, bot/map-list callbacks and engine persistence 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": 147,
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": 148,
2989
  "source_report": "docs/research/qvm-health-impulse.json",
2990
  "source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
2991
  }
docs/research/rust-normal-map-session.json ADDED
The diff for this file is too large to render. See raw diff
 
scripts/analyze_rust_normal_map_session.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 encode(s):
7
+ a=s['input'];return [*[s['before'][k] for k in ['phase','option','flags','mode','first','second','blocked','pending','saved_second','saved_first','round']],a['maximum'],a['count'],a['mutation'],int(a['override']),8,*[v for row in a['clients'] for v in row],*[v for row in a['values'] for v in row],*a['indices'],*a['flags']]
8
+ def project(s):
9
+ out={k:s[k] for k in ['state','clients_after','values','ring','mirror','middle']};out.update(post_calls=s['parts']['post']['calls'],sets=s['parts']['post']['sets'],command=next((o['text'] for o in s['parts']['map']['operations'] if o['op']=='command'),None),rotation_ops=s['parts']['rotation']['operations']);return out
10
+
11
+ def main():
12
+ b=binary();p=Path('docs/research/qvm-normal-map-session.json');data=p.read_bytes();rows=[]
13
+ for s in json.loads(data)['probes']:rows.append({'source':project(s),'rust':result(b,'normal-session',encode(s))})
14
+ write_json(Path('docs/research/rust-normal-map-session.json'),{'source_report':str(p),'source_report_sha256':hashlib.sha256(data).hexdigest(),'probes':rows,'limits':'Rust matches64 continuous original6939/249007/246963 compositions. Independent max-clients/live-level bounds preserve score-reset, session-write and final connection-scan ordering; supplied first-child field/connection changes are staged with global/cvar intentions. Invalid counts reject without partial state commits. Native first child, bot/map-list callbacks and engine persistence remain open.'})
15
+ if __name__=='__main__':main()
scripts/test_rust_normal_map_session.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-normal-map-session.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-normal-map-session.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
@@ -2690,3 +2690,20 @@ report scripts/analyze_qvm_normal_map_session.py docs/research/qvm-normal-map-se
2690
  edge docs/research/qvm-normal-map-session.json scripts/test_qvm_normal_map_session.py
2691
  group qvm-normal-map-session scripts/analyze_qvm_normal_map_session.py
2692
  edge scripts/analyze_qvm_normal_map_session.py scripts/analyze_qvm_resumer_audit.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2690
  edge docs/research/qvm-normal-map-session.json scripts/test_qvm_normal_map_session.py
2691
  group qvm-normal-map-session scripts/analyze_qvm_normal_map_session.py
2692
  edge scripts/analyze_qvm_normal_map_session.py scripts/analyze_qvm_resumer_audit.py
2693
+ edge Cargo.toml scripts/analyze_rust_normal_map_session.py
2694
+ edge Cargo.lock scripts/analyze_rust_normal_map_session.py
2695
+ edge crates/urt-gameplay/Cargo.toml scripts/analyze_rust_normal_map_session.py
2696
+ edge crates/urt-gameplay/src/lib.rs scripts/analyze_rust_normal_map_session.py
2697
+ edge crates/urt-gameplay/src/native_policy.rs scripts/analyze_rust_normal_map_session.py
2698
+ edge crates/urt-gameplay/src/bin/urt-native-policy.rs scripts/analyze_rust_normal_map_session.py
2699
+ edge docs/research/qvm-normal-map-session.json scripts/analyze_rust_normal_map_session.py
2700
+ group rust-normal-map-session scripts/analyze_rust_health_impulse.py
2701
+ group gameplay scripts/analyze_rust_normal_map_session.py
2702
+ edge crates/urt-gameplay/src/transition.rs scripts/analyze_rust_normal_map_session.py
2703
+ edge crates/urt-gameplay/src/map_commands.rs scripts/analyze_rust_normal_map_session.py
2704
+ edge crates/urt-gameplay/src/intermission.rs scripts/analyze_rust_normal_map_session.py
2705
+ edge crates/urt-gameplay/src/qvm_format.rs scripts/analyze_rust_normal_map_session.py
2706
+ report scripts/analyze_rust_normal_map_session.py docs/research/rust-normal-map-session.json
2707
+ edge docs/research/rust-normal-map-session.json scripts/test_rust_normal_map_session.py
2708
+ group rust-normal-map-session scripts/analyze_rust_normal_map_session.py
2709
+ edge scripts/analyze_rust_normal_map_session.py scripts/analyze_qvm_resumer_audit.py
xtask/src/lib.rs CHANGED
@@ -1089,6 +1089,16 @@ mod integration_tests {
1089
  ("native_policy", "post_map_clients"),
1090
  ("native_policy", "spawn_overlap"),
1091
  ("native_policy", "bot_rotation"),
 
 
 
 
 
 
 
 
 
 
1092
  ("protected", "protected_update"),
1093
  ("mode10", "mode10_entry"),
1094
  ("intercept_animation", "mode10_animation"),
@@ -1153,6 +1163,11 @@ mod integration_tests {
1153
  ("qvm-post-map-clients", "post_map_clients"),
1154
  ("qvm-spawn-overlap", "spawn_overlap"),
1155
  ("qvm-bot-rotation", "bot_rotation"),
 
 
 
 
 
1156
  ("qvm-rank-small-sort", "rank_sort"),
1157
  ("qvm-rank-large-sort", "rank_sort"),
1158
  ("qvm-ranking-complete", "ranking_complete"),
 
1089
  ("native_policy", "post_map_clients"),
1090
  ("native_policy", "spawn_overlap"),
1091
  ("native_policy", "bot_rotation"),
1092
+ ("camera_world", "queried_spawn"),
1093
+ ("camera_world", "queried_camera"),
1094
+ ("native_policy", "queried_spawn"),
1095
+ ("native_policy", "queried_camera"),
1096
+ ("native_policy", "session_write"),
1097
+ ("native_policy", "post_map_session"),
1098
+ ("native_policy", "normal_map_session"),
1099
+ ("map_commands", "normal_map_session"),
1100
+ ("transition", "normal_map_session"),
1101
+ ("qvm_format", "normal_map_session"),
1102
  ("protected", "protected_update"),
1103
  ("mode10", "mode10_entry"),
1104
  ("intercept_animation", "mode10_animation"),
 
1163
  ("qvm-post-map-clients", "post_map_clients"),
1164
  ("qvm-spawn-overlap", "spawn_overlap"),
1165
  ("qvm-bot-rotation", "bot_rotation"),
1166
+ ("qvm-queried-spawn", "queried_spawn"),
1167
+ ("qvm-queried-camera", "queried_camera"),
1168
+ ("qvm-session-write", "session_write"),
1169
+ ("qvm-post-map-session", "post_map_session"),
1170
+ ("qvm-normal-map-session", "normal_map_session"),
1171
  ("qvm-rank-small-sort", "rank_sort"),
1172
  ("qvm-rank-large-sort", "rank_sort"),
1173
  ("qvm-ranking-complete", "ranking_complete"),