nmarshall commited on
Commit
5f7b2df
·
1 Parent(s): e9347cf

gameplay: compose recovered exit rules

Browse files
crates/urt-gameplay/src/bin/urt-lifecycle.rs CHANGED
@@ -426,6 +426,77 @@ fn run() -> Result<(), String> {
426
  second.cached_ping as u32
427
  );
428
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
429
  "exit-timer" if w.len() >= 12 && w[10] <= 64 && w[11] <= 64 => {
430
  use urt_gameplay::exit_rules::{TimerHit, TimerState, apply_timer};
431
  let n = w[10] as usize;
 
426
  second.cached_ping as u32
427
  );
428
  }
429
+ "exit-complete" if w.len() >= 22 && w[20] <= 64 && w[21] <= 64 => {
430
+ use urt_gameplay::exit_rules::{
431
+ Decision, LimitHit, LimitState, State, TimerState, advance,
432
+ };
433
+ let n = w[20] as usize;
434
+ let e = w[21] as usize;
435
+ if w.len() != 22 + 10 * n + e {
436
+ return Err("invalid complete exit clients".into());
437
+ }
438
+ let mut clients: Vec<_> = w[22..22 + 10 * n]
439
+ .chunks_exact(10)
440
+ .map(exit_client)
441
+ .collect();
442
+ let mut s = State {
443
+ blocked: w[0] != 0,
444
+ pending: w[1] as i32,
445
+ slow: w[2] != 0,
446
+ predicate: w[19] != 0,
447
+ timer: TimerState {
448
+ now: w[3] as i32,
449
+ mode: w[4] as i32,
450
+ client_count: w[6] as usize,
451
+ round_limit: w[7] as i32,
452
+ round_active: w[8] != 0,
453
+ round_count: w[9] as i32,
454
+ countdown: w[10] as i32,
455
+ time_limit: w[11] as i32,
456
+ paused: w[12] != 0,
457
+ start: w[13] as i32,
458
+ },
459
+ limits: LimitState {
460
+ mode: w[4] as i32,
461
+ max_clients: w[5] as usize,
462
+ playing: w[14] as i32,
463
+ score_limit: w[15] as i32,
464
+ team_limit: w[16] as i32,
465
+ scores: [w[17] as i32, w[18] as i32],
466
+ },
467
+ };
468
+ let p = advance(&mut s, &mut clients, &w[22 + 10 * n..])
469
+ .map_err(|e| format!("unsupported input: {e:?}"))?;
470
+ let (kind, id) = match p.decision {
471
+ Decision::Blocked => ("blocked", -1),
472
+ Decision::Wait => ("wait", -1),
473
+ Decision::Transition => ("transition", -1),
474
+ Decision::Class(id) => ("class", id as i32),
475
+ Decision::Predicate => ("predicate", -1),
476
+ Decision::Round => ("round", -1),
477
+ Decision::Time => ("time", -1),
478
+ Decision::None => ("none", -1),
479
+ Decision::Limit(hit) => match hit {
480
+ LimitHit::LowFirst => ("low_first", -1),
481
+ LimitHit::LowSecond => ("low_second", -1),
482
+ LimitHit::HighFirst => ("high_first", -1),
483
+ LimitHit::HighSecond => ("high_second", -1),
484
+ LimitHit::Client(id) => ("client", id as i32),
485
+ },
486
+ };
487
+ println!(
488
+ "{{\"kind\":\"{}\",\"client\":{},\"pending\":{},\"clients\":[{}],\"resets\":[{}]}}",
489
+ kind,
490
+ id,
491
+ s.pending as u32,
492
+ clients
493
+ .iter()
494
+ .map(exit_client_json)
495
+ .collect::<Vec<_>>()
496
+ .join(","),
497
+ exit_resets_json(&p.resets)
498
+ );
499
+ }
500
  "exit-timer" if w.len() >= 12 && w[10] <= 64 && w[11] <= 64 => {
501
  use urt_gameplay::exit_rules::{TimerHit, TimerState, apply_timer};
502
  let n = w[10] as usize;
crates/urt-gameplay/src/exit_rules.rs CHANGED
@@ -16,6 +16,7 @@ pub struct Client {
16
  pub enum Error {
17
  InvalidScan,
18
  InvalidEntity,
 
19
  }
20
  fn scan(clients: &[Client], count: usize) -> Result<&[Client], Error> {
21
  if clients.len() > 64 || count > clients.len() {
@@ -184,9 +185,162 @@ pub fn apply_timer(
184
  }
185
  Ok(TimerOutcome { hit, resets })
186
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  #[cfg(test)]
188
  mod tests {
189
  use super::*;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  #[test]
191
  fn round_gate_bypasses_time_even_when_not_winning() {
192
  let s = TimerState {
 
16
  pub enum Error {
17
  InvalidScan,
18
  InvalidEntity,
19
+ InvalidState,
20
  }
21
  fn scan(clients: &[Client], count: usize) -> Result<&[Client], Error> {
22
  if clients.len() > 64 || count > clients.len() {
 
185
  }
186
  Ok(TimerOutcome { hit, resets })
187
  }
188
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
189
+ pub struct State {
190
+ pub blocked: bool,
191
+ pub pending: i32,
192
+ pub slow: bool,
193
+ pub predicate: bool,
194
+ pub timer: TimerState,
195
+ pub limits: LimitState,
196
+ }
197
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
198
+ pub enum Decision {
199
+ Blocked,
200
+ Wait,
201
+ Transition,
202
+ Class(usize),
203
+ Predicate,
204
+ Round,
205
+ Time,
206
+ Limit(LimitHit),
207
+ None,
208
+ }
209
+ #[derive(Clone, Debug, PartialEq, Eq)]
210
+ pub struct Outcome {
211
+ pub decision: Decision,
212
+ pub resets: Vec<Reset>,
213
+ }
214
+ /// Complete recovered7805 policy order; supplied predicate and effect intents
215
+ /// deliberately do not implement native transitions, allocation or messages.
216
+ pub fn advance(
217
+ state: &mut State,
218
+ clients: &mut [Client],
219
+ entity_ids: &[u32],
220
+ ) -> Result<Outcome, Error> {
221
+ if state.timer.mode != state.limits.mode {
222
+ return Err(Error::InvalidState);
223
+ }
224
+ scan(clients, state.limits.max_clients)?;
225
+ scan(clients, state.timer.client_count)?;
226
+ let simple = |decision| Outcome {
227
+ decision,
228
+ resets: vec![],
229
+ };
230
+ use crate::exit_prefix::Action;
231
+ match crate::exit_prefix::advance(
232
+ state.blocked,
233
+ &mut state.pending,
234
+ state.slow,
235
+ state.timer.now,
236
+ ) {
237
+ Action::Blocked => return Ok(simple(Decision::Blocked)),
238
+ Action::Wait => return Ok(simple(Decision::Wait)),
239
+ Action::StartTransition => return Ok(simple(Decision::Transition)),
240
+ Action::ContinueModeRules => {}
241
+ }
242
+ if let Some(id) = class_guard(state.timer.mode, clients, state.limits.max_clients)? {
243
+ return Ok(simple(Decision::Class(id)));
244
+ }
245
+ if state.predicate {
246
+ return Ok(simple(Decision::Predicate));
247
+ }
248
+ let timed = apply_timer(state.timer, clients, entity_ids)?;
249
+ if let Some(hit) = timed.hit {
250
+ return Ok(Outcome {
251
+ decision: if hit == TimerHit::Round {
252
+ Decision::Round
253
+ } else {
254
+ Decision::Time
255
+ },
256
+ resets: timed.resets,
257
+ });
258
+ }
259
+ Ok(simple(
260
+ limit(state.limits, clients)?.map_or(Decision::None, Decision::Limit),
261
+ ))
262
+ }
263
  #[cfg(test)]
264
  mod tests {
265
  use super::*;
266
+ fn state() -> State {
267
+ State {
268
+ blocked: false,
269
+ pending: 0,
270
+ slow: false,
271
+ predicate: false,
272
+ timer: TimerState {
273
+ mode: 11,
274
+ time_limit: 1,
275
+ now: 60000,
276
+ client_count: 1,
277
+ ..TimerState::default()
278
+ },
279
+ limits: LimitState {
280
+ mode: 11,
281
+ playing: 2,
282
+ score_limit: 10,
283
+ team_limit: 10,
284
+ scores: [20, 20],
285
+ max_clients: 1,
286
+ },
287
+ }
288
+ }
289
+ #[test]
290
+ fn complete_order_preserves_earlier_decision_priority() {
291
+ let mut c = [Client {
292
+ connection: 2,
293
+ class: 21,
294
+ ..Client::default()
295
+ }];
296
+ let mut s = state();
297
+ s.blocked = true;
298
+ s.pending = 59000;
299
+ s.predicate = true;
300
+ assert_eq!(
301
+ advance(&mut s, &mut c, &[100]).unwrap().decision,
302
+ Decision::Blocked
303
+ );
304
+ assert_eq!(s.pending, 59000);
305
+ s.blocked = false;
306
+ assert_eq!(
307
+ advance(&mut s, &mut c, &[100]).unwrap().decision,
308
+ Decision::Transition
309
+ );
310
+ assert_eq!(s.pending, 0);
311
+ assert_eq!(
312
+ advance(&mut s, &mut c, &[100]).unwrap().decision,
313
+ Decision::Class(0)
314
+ );
315
+ c[0].class = 0;
316
+ assert_eq!(
317
+ advance(&mut s, &mut c, &[100]).unwrap().decision,
318
+ Decision::Predicate
319
+ );
320
+ s.predicate = false;
321
+ assert_eq!(
322
+ advance(&mut s, &mut c, &[100]).unwrap().decision,
323
+ Decision::Time
324
+ );
325
+ s.timer.time_limit = 0;
326
+ assert_eq!(
327
+ advance(&mut s, &mut c, &[100]).unwrap().decision,
328
+ Decision::Limit(LimitHit::HighFirst)
329
+ );
330
+ }
331
+ #[test]
332
+ fn inconsistent_mode_state_rejects_before_mutation() {
333
+ let mut s = state();
334
+ s.pending = 59000;
335
+ s.limits.mode = 9;
336
+ let before = s;
337
+ let mut clients = [Client::default()];
338
+ assert_eq!(
339
+ advance(&mut s, &mut clients, &[100]),
340
+ Err(Error::InvalidState)
341
+ );
342
+ assert_eq!(s, before);
343
+ }
344
  #[test]
345
  fn round_gate_bypasses_time_even_when_not_winning() {
346
  let s = TimerState {
docs/FINDINGS.md CHANGED
@@ -1496,3 +1496,6 @@ RE-813–822 replaces the scoreboard decimal assumption with actual37914 executi
1496
 
1497
 
1498
  **RE-832:** Complete7805 execution now composes prefix, class, supplied predicate, round/time and limit policies with shared mutations across438 finite fixtures. [Evidence](research/QVM_EXIT_COMPLETE.md).
 
 
 
 
1496
 
1497
 
1498
  **RE-832:** Complete7805 execution now composes prefix, class, supplied predicate, round/time and limit policies with shared mutations across438 finite fixtures. [Evidence](research/QVM_EXIT_COMPLETE.md).
1499
+
1500
+
1501
+ **RE-833:** Rust now composes the full recovered7805 priority and matches438 continuous source cases, including pending clears and mode9 reset/event intentions. [Evidence](research/RUST_EXIT_COMPLETE.md).
docs/PROVISIONAL_TODO.md CHANGED
@@ -1509,3 +1509,9 @@ RE-831: Rust matches116 original timer cases, including active-round bypass, sig
1509
 
1510
 
1511
  RE-832: Complete7805 execution now composes prefix, class, supplied predicate, round/time and limit policies with shared mutations across438 finite fixtures. Full runtime completion remains open. [Evidence](research/QVM_EXIT_COMPLETE.md).
 
 
 
 
 
 
 
1509
 
1510
 
1511
  RE-832: Complete7805 execution now composes prefix, class, supplied predicate, round/time and limit policies with shared mutations across438 finite fixtures. Full runtime completion remains open. [Evidence](research/QVM_EXIT_COMPLETE.md).
1512
+
1513
+
1514
+ RE-833: Rust now composes the full recovered7805 priority and matches438 continuous source cases, including pending clears and mode9 reset/event intentions. Full runtime completion remains open. [Evidence](research/RUST_EXIT_COMPLETE.md).
1515
+
1516
+
1517
+ - [ ] RE-834: Recover supplied exit predicate/transition/native effects, compose ranking→exit→scoreboard state, and validate actual runtime traces and macOS/Linux rendering. [Batch scope](research/QVM_FLOAT_EXIT_RULES_BATCH.md).
docs/RESEARCH_LEDGER.md CHANGED
@@ -1830,3 +1830,12 @@ Rust matches116 original timer cases, including active-round bypass, signed mult
1830
  ## 2026-10-27 — RE-832 Continuous original exit-rule body with all recovered priority branches
1831
 
1832
  Complete7805 execution now composes prefix, class, supplied predicate, round/time and limit policies with shared mutations across438 finite fixtures. Focused `cargo xtask test-group exit-complete` 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.
 
 
 
 
 
 
 
 
 
 
1830
  ## 2026-10-27 — RE-832 Continuous original exit-rule body with all recovered priority branches
1831
 
1832
  Complete7805 execution now composes prefix, class, supplied predicate, round/time and limit policies with shared mutations across438 finite fixtures. Focused `cargo xtask test-group exit-complete` 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.
1833
+
1834
+
1835
+ ## 2026-10-27 — RE-833 Rust complete recovered exit-rule decision and mutation composition
1836
+
1837
+ Rust now composes the full recovered7805 priority and matches438 continuous source cases, including pending clears and mode9 reset/event intentions. Focused `cargo xtask test-group rust-exit-complete` 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.
1838
+
1839
+ RE-833 final review: added five explicit source-to-Rust dependency edges and registry regression assertions; the targeted registry test and repeated `rust-exit-complete` focused group pass. Added the ten-task batch summary and RE-834 follow-up scope.
1840
+
1841
+ Batch RE-824–833 boundary: the single `cargo xtask test-full --since 1ed43d40` run passed after task ten: 1,628 Python tests, 127 Rust tests (assets 10, gameplay 89, xtask 28), 43 deterministic report producers, storage policy, 392 documentation files and whitespace checks. No generated report drift; validation notes amended into task ten.
docs/REVERSE_ENGINEERING_TODO.md CHANGED
@@ -1546,3 +1546,9 @@ Purpose: turn the supplied installation into an evidence-backed specification of
1546
 
1547
 
1548
  - [x] RE-832 Continuous original exit-rule body with all recovered priority branches ([evidence](research/QVM_EXIT_COMPLETE.md)).
 
 
 
 
 
 
 
1546
 
1547
 
1548
  - [x] RE-832 Continuous original exit-rule body with all recovered priority branches ([evidence](research/QVM_EXIT_COMPLETE.md)).
1549
+
1550
+
1551
+ - [x] RE-833 Rust complete recovered exit-rule decision and mutation composition ([evidence](research/RUST_EXIT_COMPLETE.md)).
1552
+
1553
+
1554
+ - [ ] RE-834: Recover supplied exit predicate/transition/native effects, compose ranking→exit→scoreboard state, and validate actual runtime traces and macOS/Linux rendering. [Batch scope](research/QVM_FLOAT_EXIT_RULES_BATCH.md).
docs/TESTING.md CHANGED
@@ -128,3 +128,8 @@ Use `cargo xtask test-group <name>` with `coherent-memory`, `child-byte-effects`
128
  ## Original formatter and exit-rule groups
129
 
130
  Use `cargo xtask test-group <name>` with `format-string`, `format-integer`, `formatter`, `scoreboard-formatter`, `rust-qvm-format`, `rust-scoreboard-original`, `format-wrappers`, `rust-format-wrappers`, `exit-prefix` or `rust-exit-prefix`. The registry regression verifies source-report selections for the new Rust parity producers and both new module paths. See [contract limits and corrected formatter assumptions](research/QVM_ORIGINAL_FORMATTER_BATCH.md). Full validation remains once after ten task commits.
 
 
 
 
 
 
128
  ## Original formatter and exit-rule groups
129
 
130
  Use `cargo xtask test-group <name>` with `format-string`, `format-integer`, `formatter`, `scoreboard-formatter`, `rust-qvm-format`, `rust-scoreboard-original`, `format-wrappers`, `rust-format-wrappers`, `exit-prefix` or `rust-exit-prefix`. The registry regression verifies source-report selections for the new Rust parity producers and both new module paths. See [contract limits and corrected formatter assumptions](research/QVM_ORIGINAL_FORMATTER_BATCH.md). Full validation remains once after ten task commits.
131
+
132
+
133
+ ## Float formatter and complete exit-rule groups
134
+
135
+ Use `cargo xtask test-group <name>` with `format-float`, `rust-format-float`, `exit-class`, `rust-exit-class`, `exit-limits`, `rust-exit-limits`, `exit-timers`, `rust-exit-timers`, `exit-complete` or `rust-exit-complete`. The registry regression verifies each source-report dependency selects its Rust adapter and test. Scoped gameplay checks refresh test-count metadata. Keep focused validation before each commit and one full run after ten commits. See the [batch evidence and remaining effects](research/QVM_FLOAT_EXIT_RULES_BATCH.md).
docs/research/QVM_FLOAT_EXIT_RULES_BATCH.md ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Float formatter and exit-rule composition: RE-824–833
2
+
3
+ Ten tasks recover original float formatting and compose the exit-rule body 7805 into Rust. These are bounded source contracts, not a running game or complete native parity.
4
+
5
+ | Task | Result |
6
+ | --- | --- |
7
+ | RE-824 | [Float formatter source arithmetic](QVM_FORMAT_FLOAT.md) |
8
+ | RE-825 | [Rust float formatter](RUST_FORMAT_FLOAT.md) |
9
+ | RE-826 | [Mode11 class guard](QVM_EXIT_CLASS.md) |
10
+ | RE-827 | [Rust class guard](RUST_EXIT_CLASS.md) |
11
+ | RE-828 | [Score/team exit limit priority](QVM_EXIT_LIMITS.md) |
12
+ | RE-829 | [Rust limit policy](RUST_EXIT_LIMITS.md) |
13
+ | RE-830 | [Round/time gates and mode 9 reset effects](QVM_EXIT_TIMERS.md) |
14
+ | RE-831 | [Rust timers and reset intentions](RUST_EXIT_TIMERS.md) |
15
+ | RE-832 | [Continuous exit-rule source composition](QVM_EXIT_COMPLETE.md) |
16
+ | RE-833 | [Rust complete recovered exit policy](RUST_EXIT_COMPLETE.md) |
17
+
18
+ ## Evidence and boundaries
19
+
20
+ The float helper has 126 fixtures, plus 12 central formatter fixtures. It truncates repeated f32 fractional arithmetic; width applies to the integer portion. Negative zero loses its sign. Rust rejects nonfinite/out-of-domain values and bounds precision and width explicitly.
21
+
22
+ Exit class, limit and timer contracts have 45, 125 and 116 fixtures respectively. Continuous 7805 execution covers 438 cases, including conflicting priorities and shared client mutations. Rust matches those decisions, pending-state updates and reset intentions. The active round gate bypasses time-limit evaluation; the mode 9 path distinguishes collected client count from configured scan bounds.
23
+
24
+ Child 7662 remains a supplied predicate. Calls 7525/6825/7367, formatting, sending, logging and event allocation are captured or supplied effects. Event 69 intentions expose owner/time and flag OR 256; the source fixture supplies initial constructor flags 16. Neither adapter establishes the real allocator or transition implementation. Native/JIT execution, real game traces and unrestricted memory legality remain unverified.
25
+
26
+ ## Validation
27
+
28
+ Focused cargo xtask groups passed before each task commit. Reports were regenerated twice with deterministic output, relevant Python and scoped Rust checks, storage policy, documentation links and whitespace validation. After commit ten, `cargo xtask test-full --since 1ed43d40` passed: 1,628 Python tests, 127 Rust tests (assets 10, gameplay 89, xtask 28), 43 affected producers regenerated twice deterministically, storage policy, 392 documentation files and whitespace checks. Generated reports left no uncommitted differences. This was the only full-suite invocation in the batch.
29
+
30
+ ## Next work: RE-834
31
+
32
+ - [ ] Recover 7662 and replace the supplied predicate with original instruction evidence.
33
+ - [ ] Recover 7525/6825/7367 transition effects and their world/state ownership.
34
+ - [ ] Resolve formatting, copy, log, send and event constructors used by these paths.
35
+ - [ ] Integrate ranking, exit decisions and scoreboard publication across shared state.
36
+ - [ ] Audit legacy trace/memory assumptions and compare actual native/gameplay traces.
37
+ - [ ] Connect verified policies to the game runtime and validate macOS/Linux Bevy/Solari behavior.
docs/research/README.md CHANGED
@@ -1086,3 +1086,8 @@ The extension collector adds five focused tests for native tail calls, command-r
1086
 
1087
 
1088
  [RE-832: Continuous original exit-rule body with all recovered priority branches](QVM_EXIT_COMPLETE.md).
 
 
 
 
 
 
1086
 
1087
 
1088
  [RE-832: Continuous original exit-rule body with all recovered priority branches](QVM_EXIT_COMPLETE.md).
1089
+
1090
+
1091
+ [RE-833: Rust complete recovered exit-rule decision and mutation composition](RUST_EXIT_COMPLETE.md).
1092
+
1093
+ - [RE-824–833 float formatter and exit-rule batch](QVM_FLOAT_EXIT_RULES_BATCH.md).
docs/research/RUST_EXIT_COMPLETE.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Rust complete recovered exit-rule decision and mutation composition
2
+
3
+ RE-833. Reproduce with `python3 scripts/analyze_rust_exit_complete.py`; focused validation: `cargo xtask test-group rust-exit-complete`. [Machine report](rust-exit-complete.json).
4
+
5
+ The typed state validates safe scan bounds and consistent mode inputs, then composes prefix, class, supplied predicate, timers and limit policies. Source parity checks every decision, offender, pending word, client field and reset intention, with an independent composition oracle. Unit tests stress conflicting priorities and rejection before mutation. Actual predicate/transition/allocation/log/send effects and full game runtime integration remain open.
6
+
7
+ Original executable/native runtime and complete Linux/macOS Rust/Bevy/Solari acceptance remain unverified.
docs/research/rust-exit-complete.json ADDED
The diff for this file is too large to render. See raw diff
 
docs/research/rust-health-impulse.json CHANGED
@@ -2985,7 +2985,7 @@
2985
  "exit_code": 2
2986
  }
2987
  ],
2988
- "rust_unit_tests": 87,
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": 89,
2989
  "source_report": "docs/research/qvm-health-impulse.json",
2990
  "source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
2991
  }
scripts/analyze_rust_exit_complete.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ import hashlib,json
3
+ from pathlib import Path
4
+ from analyze_rust_exit_class import words
5
+ from analyze_rust_exit_limits import decode
6
+ from qvm_exit_rules_support import GLOBALS
7
+ from qvm_notification_support import CPOOL,POOL
8
+ from rust_lifecycle_support import binary,result
9
+ from analyze_qvm import write_json
10
+
11
+ def outcome(s):
12
+ req=s['requests'];a=s['input'];kind='none';id=-1
13
+ if not req:kind='wait'
14
+ elif req[0]['target']==7525:kind='blocked'
15
+ elif req[0]['target']==6825:kind='transition'
16
+ elif req[0]['target']==7367:kind='class';id=(req[1]['arguments']['20']-CPOOL-504)//6336
17
+ elif a['predicate']:kind='predicate'
18
+ elif len(req)>1:
19
+ if req[-1]['arguments']['8'] in [34577,34461]:kind='round' if req[-1]['arguments']['8']==34577 else 'time'
20
+ else:decoded=decode(req[1:]);kind=decoded['kind'];id=decoded['client']
21
+ resets=[]
22
+ for call,event,log in zip([r for r in req if r['target']==277695],s['events'],[r for r in req if r['target']==7259]):
23
+ entity=(call['arguments']['8']-POOL-24)//880;slot=next(j for j,c in enumerate(s['clients_before']) if c['140']==entity);resets.append({'client':slot,'entity':entity,'owner':event['fields']['140'],'time':event['fields']['84'],'kind':69,'flags_or':event['fields']['424']&256,'logged_value':log['arguments']['16']})
24
+ return {'kind':kind,'client':id,'pending':s['pending_after'],'clients':s['clients_after'],'resets':resets}
25
+
26
+ def main():
27
+ b=binary();p=Path('docs/research/qvm-exit-complete.json');data=p.read_bytes();rows=[]
28
+ for i,s in enumerate(json.loads(data)['probes']):
29
+ a=s['input'];c=s['clients_before'];args=[a[k] for k in GLOBALS]+[a['predicate'],len(c),64,*[v for j,c in enumerate(c) for v in words(c,j)],*[100+j for j in range(64)]];rows.append({'case':i,'source':outcome(s),'rust':result(b,'exit-complete',args)})
30
+ write_json(Path('docs/research/rust-exit-complete.json'),{'source_report':str(p),'source_report_sha256':hashlib.sha256(data).hexdigest(),'probes':rows,'limits':'Rust complete recovered7805 decision and shared mutation contract matches438 continuous original source fixtures.7662 predicate is supplied, event allocation and all transition/log/format/send effects remain intentions. Invalid raw pointer/count ABI and native/runtime acceptance remain open.'})
31
+ if __name__=='__main__':main()
scripts/test_rust_exit_complete.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib,json,unittest
2
+ from pathlib import Path
3
+ from test_qvm_exit_complete import oracle,signed
4
+ from test_qvm_exit_limits import decision
5
+ class RustCompleteExitTests(unittest.TestCase):
6
+ def test_exact_source_outputs_and_independent_composition(self):
7
+ r=json.loads(Path('docs/research/rust-exit-complete.json').read_text());source=json.loads(Path(r['source_report']).read_text());self.assertEqual(len(r['probes']),438)
8
+ for p in r['probes']:
9
+ s=source['probes'][p['case']];pending,req,ids,kind=oracle(s['input'],s['clients_before']);self.assertEqual(p['rust'],p['source']);self.assertEqual(p['rust']['pending'],pending);self.assertEqual(p['rust']['kind'],kind);self.assertEqual(p['rust']['clients'],s['clients_after']);self.assertEqual(len(p['rust']['resets']),len(ids))
10
+ if kind=='client':self.assertEqual(p['rust']['client'],decision(s['input'],[{k:signed(v) for k,v in c.items()} for c in s['clients_before']])[1])
11
+ def test_source_identity(self):
12
+ r=json.loads(Path('docs/research/rust-exit-complete.json').read_text());self.assertEqual(hashlib.sha256(Path(r['source_report']).read_bytes()).hexdigest(),r['source_report_sha256'])
13
+ if __name__=='__main__':unittest.main()
xtask/checks.tsv CHANGED
@@ -2136,3 +2136,21 @@ report scripts/analyze_qvm_exit_complete.py docs/research/qvm-exit-complete.json
2136
  edge docs/research/qvm-exit-complete.json scripts/test_qvm_exit_complete.py
2137
  group exit-complete scripts/analyze_qvm_exit_complete.py
2138
  edge scripts/analyze_qvm_exit_complete.py scripts/analyze_qvm_resumer_audit.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2136
  edge docs/research/qvm-exit-complete.json scripts/test_qvm_exit_complete.py
2137
  group exit-complete scripts/analyze_qvm_exit_complete.py
2138
  edge scripts/analyze_qvm_exit_complete.py scripts/analyze_qvm_resumer_audit.py
2139
+ edge Cargo.toml scripts/analyze_rust_exit_complete.py
2140
+ edge Cargo.lock scripts/analyze_rust_exit_complete.py
2141
+ edge crates/urt-gameplay/Cargo.toml scripts/analyze_rust_exit_complete.py
2142
+ edge crates/urt-gameplay/src/lib.rs scripts/analyze_rust_exit_complete.py
2143
+ edge crates/urt-gameplay/src/bin/urt-lifecycle.rs scripts/analyze_rust_exit_complete.py
2144
+ edge crates/urt-gameplay/src/exit_rules.rs scripts/analyze_rust_exit_complete.py
2145
+ edge crates/urt-gameplay/src/exit_prefix.rs scripts/analyze_rust_exit_complete.py
2146
+ group gameplay scripts/analyze_rust_exit_complete.py
2147
+ group rust-exit-complete scripts/analyze_rust_health_impulse.py
2148
+ report scripts/analyze_rust_exit_complete.py docs/research/rust-exit-complete.json
2149
+ edge docs/research/rust-exit-complete.json scripts/test_rust_exit_complete.py
2150
+ group rust-exit-complete scripts/analyze_rust_exit_complete.py
2151
+ edge scripts/analyze_rust_exit_complete.py scripts/analyze_qvm_resumer_audit.py
2152
+ edge docs/research/qvm-format-float.json scripts/analyze_rust_format_float.py
2153
+ edge docs/research/qvm-exit-class.json scripts/analyze_rust_exit_class.py
2154
+ edge docs/research/qvm-exit-limits.json scripts/analyze_rust_exit_limits.py
2155
+ edge docs/research/qvm-exit-timers.json scripts/analyze_rust_exit_timers.py
2156
+ edge docs/research/qvm-exit-complete.json scripts/analyze_rust_exit_complete.py
xtask/src/lib.rs CHANGED
@@ -1098,6 +1098,11 @@ mod integration_tests {
1098
  ("qvm-scoreboard-formatter", "scoreboard_packet"),
1099
  ("qvm-format-wrappers", "format_wrappers"),
1100
  ("qvm-exit-prefix", "exit_prefix"),
 
 
 
 
 
1101
  ] {
1102
  let selected = plan(
1103
  &g,
 
1098
  ("qvm-scoreboard-formatter", "scoreboard_packet"),
1099
  ("qvm-format-wrappers", "format_wrappers"),
1100
  ("qvm-exit-prefix", "exit_prefix"),
1101
+ ("qvm-format-float", "format_float"),
1102
+ ("qvm-exit-class", "exit_class"),
1103
+ ("qvm-exit-limits", "exit_limits"),
1104
+ ("qvm-exit-timers", "exit_timers"),
1105
+ ("qvm-exit-complete", "exit_complete"),
1106
  ] {
1107
  let selected = plan(
1108
  &g,