nmarshall commited on
Commit
d048ff6
·
1 Parent(s): 23ce463

gameplay: port recent record awards

Browse files
crates/urt-gameplay/src/bin/urt-lifecycle.rs CHANGED
@@ -223,6 +223,52 @@ fn run() -> Result<(), String> {
223
  n.ratio.to_bits()
224
  );
225
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
226
  _ => return Err("unsupported contract or arguments".into()),
227
  }
228
  Ok(())
 
223
  n.ratio.to_bits()
224
  );
225
  }
226
+ "award" if w.len() == 7 && w[6] <= 1 => {
227
+ use urt_gameplay::recent_awards::{Client, Rows, award};
228
+ let index = w[1] as i32;
229
+ let key = if (0..=64).contains(&index) {
230
+ index as usize
231
+ } else {
232
+ 0
233
+ };
234
+ let mut rows: Rows = std::array::from_fn(|j| {
235
+ if [0, 1, 2, 3, 64].contains(&j) && (j != 2 || w[6] != 0) {
236
+ let mut pairs = [[0; 2]; 65];
237
+ pairs[64][0] = w[4] as i32;
238
+ pairs[key] = [w[2] as i32, (w[5] as i32).wrapping_sub(w[3] as i32)];
239
+ Some(Client {
240
+ id: j as i32,
241
+ team: if j == 1 || j == 64 { 1 } else { 2 },
242
+ pairs,
243
+ })
244
+ } else {
245
+ None
246
+ }
247
+ });
248
+ let awards = award(w[0] as i32, index, 2, w[5] as i32, &mut rows)
249
+ .map_err(|e| format!("unsupported input: {e:?}"))?;
250
+ let intents: Vec<_> = awards
251
+ .iter()
252
+ .map(|a| format!("[{},{},{}]", a.recipient, a.offender, a.victim))
253
+ .collect();
254
+ let values: Vec<_> = rows
255
+ .iter()
256
+ .enumerate()
257
+ .filter_map(|(j, r)| {
258
+ r.as_ref().map(|r| {
259
+ format!(
260
+ "{{\"slot\":{},\"awards\":{},\"count\":{}}}",
261
+ j, r.pairs[64][0] as u32, r.pairs[key][0] as u32
262
+ )
263
+ })
264
+ })
265
+ .collect();
266
+ println!(
267
+ "{{\"awards\":[{}],\"rows\":[{}]}}",
268
+ intents.join(","),
269
+ values.join(",")
270
+ );
271
+ }
272
  _ => return Err("unsupported contract or arguments".into()),
273
  }
274
  Ok(())
crates/urt-gameplay/src/lib.rs CHANGED
@@ -149,6 +149,7 @@ pub mod intercept_animation;
149
  pub mod method_stats;
150
  pub mod mode10;
151
  pub mod protected;
 
152
  pub mod record_clear;
153
  pub mod score;
154
  pub mod stat_totals;
 
149
  pub mod method_stats;
150
  pub mod mode10;
151
  pub mod protected;
152
+ pub mod recent_awards;
153
  pub mod record_clear;
154
  pub mod score;
155
  pub mod stat_totals;
crates/urt-gameplay/src/recent_awards.rs ADDED
@@ -0,0 +1,130 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Source285227 record eligibility and award intent, without logging/networking.
2
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
3
+ pub struct Client {
4
+ pub id: i32,
5
+ pub team: i32,
6
+ pub pairs: [[i32; 2]; 65],
7
+ }
8
+ pub type Rows = [Option<Client>; 65];
9
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
10
+ pub struct Award {
11
+ pub recipient: i32,
12
+ pub offender: i32,
13
+ pub victim: i32,
14
+ }
15
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
16
+ pub enum AwardError {
17
+ UnsupportedOffender,
18
+ }
19
+ /// Rows0..63 are recipients; row64 is accepted as the victim. Pair64[0]
20
+ /// aliases the award counter, preserving the source3032 storage overlap.
21
+ pub fn award(
22
+ mode: i32,
23
+ index: i32,
24
+ offender: i32,
25
+ now: i32,
26
+ rows: &mut Rows,
27
+ ) -> Result<Vec<Award>, AwardError> {
28
+ let Ok(index) = usize::try_from(index) else {
29
+ return Ok(vec![]);
30
+ };
31
+ if index > 64 || mode < 3 || mode == 9 || mode == 11 {
32
+ return Ok(vec![]);
33
+ }
34
+ let Some(victim) = rows[index].as_ref() else {
35
+ return Ok(vec![]);
36
+ };
37
+ let (victim_id, victim_team) = (victim.id, victim.team);
38
+ let actor_index = usize::try_from(offender)
39
+ .ok()
40
+ .filter(|i| *i <= 64)
41
+ .ok_or(AwardError::UnsupportedOffender)?;
42
+ let Some(actor) = rows[actor_index].as_ref() else {
43
+ return Ok(vec![]);
44
+ };
45
+ let actor_id = actor.id;
46
+ let mut requests = vec![];
47
+ for row in rows.iter_mut().take(64).flatten() {
48
+ if row.id == offender
49
+ || row.team == victim_team
50
+ || row.pairs[index][0] < 40
51
+ || now.wrapping_sub(row.pairs[index][1]) > 30000
52
+ {
53
+ continue;
54
+ }
55
+ row.pairs[64][0] = row.pairs[64][0].wrapping_add(1);
56
+ requests.push(Award {
57
+ recipient: row.id,
58
+ offender: actor_id,
59
+ victim: victim_id,
60
+ });
61
+ }
62
+ Ok(requests)
63
+ }
64
+ #[cfg(test)]
65
+ mod tests {
66
+ use super::*;
67
+ fn rows() -> Rows {
68
+ std::array::from_fn(|j| {
69
+ if [0, 1, 2, 64].contains(&j) {
70
+ let mut pairs = [[0; 2]; 65];
71
+ pairs[1] = [40, 10000];
72
+ pairs[64] = [40, 10000];
73
+ Some(Client {
74
+ id: j as i32,
75
+ team: if j == 1 || j == 64 { 1 } else { 2 },
76
+ pairs,
77
+ })
78
+ } else {
79
+ None
80
+ }
81
+ })
82
+ }
83
+ #[test]
84
+ fn inclusive64_and_alias_counter() {
85
+ let mut r = rows();
86
+ assert_eq!(
87
+ award(10, 64, 2, 40000, &mut r).unwrap(),
88
+ vec![Award {
89
+ recipient: 0,
90
+ offender: 2,
91
+ victim: 64
92
+ }]
93
+ );
94
+ assert_eq!(r[0].unwrap().pairs[64], [41, 10000]);
95
+ }
96
+ #[test]
97
+ fn future_timestamp_and_counter_wrap() {
98
+ let mut r = rows();
99
+ r[0].as_mut().unwrap().pairs[1][1] = 40001;
100
+ r[0].as_mut().unwrap().pairs[64][0] = -1;
101
+ assert_eq!(award(10, 1, 2, 40000, &mut r).unwrap().len(), 1);
102
+ assert_eq!(r[0].unwrap().pairs[64][0], 0);
103
+ }
104
+ #[test]
105
+ fn strict_age_and_count_thresholds() {
106
+ let mut r = rows();
107
+ let original = r;
108
+ r[0].as_mut().unwrap().pairs[1][1] = 9999;
109
+ assert!(award(10, 1, 2, 40000, &mut r).unwrap().is_empty());
110
+ let mut r = original;
111
+ r[0].as_mut().unwrap().pairs[1][0] = 39;
112
+ assert!(award(10, 1, 2, 40000, &mut r).unwrap().is_empty());
113
+ }
114
+ #[test]
115
+ fn gates_missing_actor_and_unsupported_offender() {
116
+ for (mode, index) in [(9, 1), (11, 1), (2, 1), (10, -1), (10, 65)] {
117
+ let mut r = rows();
118
+ let before = r;
119
+ assert!(award(mode, index, -1, 40000, &mut r).unwrap().is_empty());
120
+ assert_eq!(r, before);
121
+ }
122
+ let mut r = rows();
123
+ r[2] = None;
124
+ assert!(award(10, 1, 2, 40000, &mut r).unwrap().is_empty());
125
+ assert_eq!(
126
+ award(10, 1, -1, 40000, &mut r),
127
+ Err(AwardError::UnsupportedOffender)
128
+ );
129
+ }
130
+ }
docs/ASSET_PIPELINE_TODO.md CHANGED
@@ -955,3 +955,6 @@ Research progress: RE-764 [report](research/QVM_RANKING_SETUP.md); original asse
955
 
956
 
957
  Research progress: RE-765 [report](research/RUST_STAT_TOTALS.md); original assets unchanged.
 
 
 
 
955
 
956
 
957
  Research progress: RE-765 [report](research/RUST_STAT_TOTALS.md); original assets unchanged.
958
+
959
+
960
+ Research progress: RE-766 [report](research/RUST_RECENT_AWARDS.md); original assets unchanged.
docs/FINDINGS.md CHANGED
@@ -1301,3 +1301,6 @@ RE-725–734 batch: [health, contact and Rust impulse findings](research/QVM_HEA
1301
 
1302
 
1303
  **RE-765 — Rust counter totals divisor and normalized ratio primitives:** Rust totals and normalized ratios match80 total,28 ratio and38 signed-divisor source fixtures while retaining20 slots. [Evidence](research/RUST_STAT_TOTALS.md).
 
 
 
 
1301
 
1302
 
1303
  **RE-765 — Rust counter totals divisor and normalized ratio primitives:** Rust totals and normalized ratios match80 total,28 ratio and38 signed-divisor source fixtures while retaining20 slots. [Evidence](research/RUST_STAT_TOTALS.md).
1304
+
1305
+
1306
+ **RE-766 — Rust recent record awards timestamp gates and alias storage:** Rust recent-record awards match68 source fixtures, preserving future ages, wrapped clocks, ID gates and index64 award/count aliasing. [Evidence](research/RUST_RECENT_AWARDS.md).
docs/PROVISIONAL_TODO.md CHANGED
@@ -1296,3 +1296,6 @@ Research progress: RE-764 [report](research/QVM_RANKING_SETUP.md); full native/p
1296
 
1297
 
1298
  Research progress: RE-765 [report](research/RUST_STAT_TOTALS.md); full native/platform/game scope remains open.
 
 
 
 
1296
 
1297
 
1298
  Research progress: RE-765 [report](research/RUST_STAT_TOTALS.md); full native/platform/game scope remains open.
1299
+
1300
+
1301
+ Research progress: RE-766 [report](research/RUST_RECENT_AWARDS.md); full native/platform/game scope remains open.
docs/RESEARCH_LEDGER.md CHANGED
@@ -1481,3 +1481,8 @@ Additional RE-763 regression: `cargo xtask test-group recipient-single` passes6
1481
  ## 2026-10-21 — RE-765 Rust counter totals divisor and normalized ratio primitives
1482
 
1483
  Rust totals and normalized ratios match80 total,28 ratio and38 signed-divisor source fixtures while retaining20 slots. Focused `cargo xtask test-group gameplay` passes (25 Python tests); selected reports regenerate twice byte-identically, with storage, links and whitespace guards. New report: 32451 bytes. Full-suite validation is deferred to the tenth batch commit. Rust gameplay crate changed; focused gameplay regenerates eight parity contracts and scoped package tests. No workspace/full-suite run before commit ten.
 
 
 
 
 
 
1481
  ## 2026-10-21 — RE-765 Rust counter totals divisor and normalized ratio primitives
1482
 
1483
  Rust totals and normalized ratios match80 total,28 ratio and38 signed-divisor source fixtures while retaining20 slots. Focused `cargo xtask test-group gameplay` passes (25 Python tests); selected reports regenerate twice byte-identically, with storage, links and whitespace guards. New report: 32451 bytes. Full-suite validation is deferred to the tenth batch commit. Rust gameplay crate changed; focused gameplay regenerates eight parity contracts and scoped package tests. No workspace/full-suite run before commit ten.
1484
+
1485
+
1486
+ ## 2026-10-21 — RE-766 Rust recent record awards timestamp gates and alias storage
1487
+
1488
+ Rust recent-record awards match68 source fixtures, preserving future ages, wrapped clocks, ID gates and index64 award/count aliasing. Focused `cargo xtask test-group gameplay` passes (28 Python tests); selected reports regenerate twice byte-identically, with storage, links and whitespace guards. New report: 143080 bytes. Full-suite validation is deferred to the tenth batch commit. Rust gameplay crate changed; focused gameplay regenerates nine parity contracts and scoped package tests. Full workspace suite remains deferred to commit ten.
docs/REVERSE_ENGINEERING_TODO.md CHANGED
@@ -1339,3 +1339,6 @@ Purpose: turn the supplied installation into an evidence-backed specification of
1339
 
1340
 
1341
  - [x] RE-765 Rust counter totals divisor and normalized ratio primitives ([report](research/RUST_STAT_TOTALS.md)).
 
 
 
 
1339
 
1340
 
1341
  - [x] RE-765 Rust counter totals divisor and normalized ratio primitives ([report](research/RUST_STAT_TOTALS.md)).
1342
+
1343
+
1344
+ - [x] RE-766 Rust recent record awards timestamp gates and alias storage ([report](research/RUST_RECENT_AWARDS.md)).
docs/research/README.md CHANGED
@@ -891,3 +891,6 @@ The extension collector adds five focused tests for native tail calls, command-r
891
 
892
 
893
  [RE-765: Rust counter totals divisor and normalized ratio primitives](RUST_STAT_TOTALS.md).
 
 
 
 
891
 
892
 
893
  [RE-765: Rust counter totals divisor and normalized ratio primitives](RUST_STAT_TOTALS.md).
894
+
895
+
896
+ [RE-766: Rust recent record awards timestamp gates and alias storage](RUST_RECENT_AWARDS.md).
docs/research/RUST_RECENT_AWARDS.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Rust recent record awards timestamp gates and alias storage
2
+
3
+ RE-766. Reproduce with `python3 scripts/analyze_rust_recent_awards.py`; validate with `cargo xtask test-group gameplay`. See the [machine report](rust-recent-awards.json).
4
+
5
+ Added typed65-row records,64-recipient scan, source signed/wrapping eligibility and returned log ID intents. Pair64[0] is the award counter rather than separate storage. Four Rust unit tests cover equality, future timestamps, count/counter wrap, empty/invalid gates and explicit unsupported offender indices. Log formatting, record lifetime ownership and native/platform parity remain open.
6
+
7
+ No original executable/full VM/native world collision or rendering runs. Supplied callback/native responses and fixture domains remain explicit; complete macOS/Linux Rust/Bevy/Solari gameplay remains open.
docs/research/rust-health-impulse.json CHANGED
@@ -2985,7 +2985,7 @@
2985
  "exit_code": 2
2986
  }
2987
  ],
2988
- "rust_unit_tests": 34,
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": 38,
2989
  "source_report": "docs/research/qvm-health-impulse.json",
2990
  "source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
2991
  }
docs/research/rust-recent-awards.json ADDED
The diff for this file is too large to render. See raw diff
 
scripts/analyze_rust_recent_awards.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Rust recent-award records and logging intent against source285227 fixtures."""
3
+ import hashlib,json
4
+ from pathlib import Path
5
+ from rust_lifecycle_support import binary,result
6
+ from analyze_qvm import write_json
7
+
8
+ def main():
9
+ b=binary();path=Path('docs/research/qvm-recent-awards.json');data=path.read_bytes();rows=[]
10
+ for p in json.loads(data)['probes']:
11
+ a=p['input'];args=[a.get('mode',10),a['index'],a.get('count',40),a.get('age',30000),a.get('initial',7),a.get('now',40000),int(a.get('actor_client',True))];expected={'awards':[[c['arguments'][str(k)] for k in [12,16,20]] for c in p['requests']],'rows':[{k:r[k] for k in ['slot','awards','count']} for r in p['final'] if r['present']]};rows.append({'input':a,'initial':p['initial'],'source':expected,'rust':result(b,'award',args)})
12
+ write_json(Path('docs/research/rust-recent-awards.json'),{'source_report':str(path),'source_report_sha256':hashlib.sha256(data).hexdigest(),'probes':rows,'limits':'Rust numeric recent awards and log ID intents match68 source fixtures.65 rows support victim64; only64 recipients are scanned. Pair64[0] shares award/count storage. Unsupported offender indices explicitly reject; live lifetime ownership/log formatting/native/platform parity remain open.'})
13
+ if __name__=='__main__':main()
scripts/test_rust_recent_awards.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib,json,unittest
2
+ from pathlib import Path
3
+ from test_qvm_recent_awards import awarded
4
+ class RustRecentAwardTests(unittest.TestCase):
5
+ @classmethod
6
+ def setUpClass(cls):cls.r=json.loads(Path('docs/research/rust-recent-awards.json').read_text())
7
+ def test_exact_source_records_and_intents(self):
8
+ self.assertEqual(len(self.r['probes']),68)
9
+ for p in self.r['probes']:self.assertEqual(p['rust'],p['source'],p['input'])
10
+ def test_independent_gates_and_identifier_intent(self):
11
+ for p in self.r['probes']:self.assertEqual(p['rust']['awards'],[[j,2,p['input']['index']] for j in awarded(p)])
12
+ def test_source_identity(self):self.assertEqual(hashlib.sha256(Path(self.r['source_report']).read_bytes()).hexdigest(),self.r['source_report_sha256'])
13
+ if __name__=='__main__':unittest.main()
xtask/checks.tsv CHANGED
@@ -1303,3 +1303,13 @@ edge crates/urt-gameplay/src/stat_totals.rs scripts/analyze_rust_health_impulse.
1303
  group gameplay scripts/analyze_rust_stat_totals.py
1304
  report scripts/analyze_rust_stat_totals.py docs/research/rust-stat-totals.json
1305
  edge docs/research/rust-stat-totals.json scripts/test_rust_stat_totals.py
 
 
 
 
 
 
 
 
 
 
 
1303
  group gameplay scripts/analyze_rust_stat_totals.py
1304
  report scripts/analyze_rust_stat_totals.py docs/research/rust-stat-totals.json
1305
  edge docs/research/rust-stat-totals.json scripts/test_rust_stat_totals.py
1306
+ edge Cargo.toml scripts/analyze_rust_recent_awards.py
1307
+ edge Cargo.lock scripts/analyze_rust_recent_awards.py
1308
+ edge crates/urt-gameplay/Cargo.toml scripts/analyze_rust_recent_awards.py
1309
+ edge crates/urt-gameplay/src/lib.rs scripts/analyze_rust_recent_awards.py
1310
+ edge crates/urt-gameplay/src/recent_awards.rs scripts/analyze_rust_recent_awards.py
1311
+ edge crates/urt-gameplay/src/bin/urt-lifecycle.rs scripts/analyze_rust_recent_awards.py
1312
+ edge crates/urt-gameplay/src/recent_awards.rs scripts/analyze_rust_health_impulse.py
1313
+ group gameplay scripts/analyze_rust_recent_awards.py
1314
+ report scripts/analyze_rust_recent_awards.py docs/research/rust-recent-awards.json
1315
+ edge docs/research/rust-recent-awards.json scripts/test_rust_recent_awards.py