nmarshall commited on
Commit
1c83d71
·
1 Parent(s): 3ad153d

gameplay: port raw ranking contracts

Browse files
crates/urt-gameplay/src/bin/urt-lifecycle.rs CHANGED
@@ -314,6 +314,53 @@ fn run() -> Result<(), String> {
314
  second.cached_ping as u32
315
  );
316
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
317
  _ => return Err("unsupported contract or arguments".into()),
318
  }
319
  Ok(())
 
314
  second.cached_ping as u32
315
  );
316
  }
317
+ "rank-compare" if w.len() == 21 => {
318
+ let client = |v: &[u32]| urt_gameplay::ranking::Client {
319
+ spectator_state: v[0] as i32,
320
+ follow_index: v[1] as i32,
321
+ connection: v[2] as i32,
322
+ team: v[3] as i32,
323
+ spectator_time: v[4] as i32,
324
+ time: v[5] as i32,
325
+ class: v[6] as i32,
326
+ score: v[7] as i32,
327
+ deaths: v[8] as i32,
328
+ awards: v[9] as i32,
329
+ };
330
+ println!(
331
+ "{{\"result\":{}}}",
332
+ urt_gameplay::ranking::compare(w[0] as i32, client(&w[1..11]), client(&w[11..]))
333
+ );
334
+ }
335
+ "rank-words" if w.len() >= 4 && w[3] <= 6 => {
336
+ let n = w[3] as usize;
337
+ if w.len() != 4 + 11 * n {
338
+ return Err("invalid ranking words".into());
339
+ }
340
+ let client = |v: &[u32]| urt_gameplay::ranking::Client {
341
+ spectator_state: v[0] as i32,
342
+ follow_index: v[1] as i32,
343
+ connection: v[2] as i32,
344
+ team: v[3] as i32,
345
+ spectator_time: v[4] as i32,
346
+ time: v[5] as i32,
347
+ class: v[6] as i32,
348
+ score: v[7] as i32,
349
+ deaths: v[8] as i32,
350
+ awards: v[9] as i32,
351
+ };
352
+ let clients: Vec<_> = w[4 + n..].chunks_exact(10).map(client).collect();
353
+ let order: Vec<_> = w[4..4 + n].iter().map(|v| *v as usize).collect();
354
+ let ranks = urt_gameplay::ranking::rank_words(
355
+ w[0] as i32,
356
+ &clients,
357
+ &order,
358
+ [w[1] as i32, w[2] as i32],
359
+ )
360
+ .map_err(|e| format!("unsupported input: {e:?}"))?;
361
+ let ranks: Vec<_> = ranks.iter().map(|v| v.to_string()).collect();
362
+ println!("{{\"ranks\":[{}]}}", ranks.join(","));
363
+ }
364
  _ => return Err("unsupported contract or arguments".into()),
365
  }
366
  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 recent_awards;
153
  pub mod recipient_payload;
154
  pub mod record_clear;
 
149
  pub mod method_stats;
150
  pub mod mode10;
151
  pub mod protected;
152
+ pub mod ranking;
153
  pub mod recent_awards;
154
  pub mod recipient_payload;
155
  pub mod record_clear;
crates/urt-gameplay/src/ranking.rs ADDED
@@ -0,0 +1,236 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ //! Source5441 raw comparator and5864 finite eligible-client rank publication.
2
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
3
+ pub struct Client {
4
+ pub spectator_state: i32,
5
+ pub follow_index: i32,
6
+ pub connection: i32,
7
+ pub team: i32,
8
+ pub spectator_time: i32,
9
+ pub time: i32,
10
+ pub class: i32,
11
+ pub score: i32,
12
+ pub deaths: i32,
13
+ pub awards: i32,
14
+ }
15
+ /// Raw source result deliberately does not implement Ord: two deferred or
16
+ /// connecting clients can compare+1 in both directions, including self.
17
+ pub fn compare(mode: i32, a: Client, b: Client) -> i32 {
18
+ if a.spectator_state == 3 || a.follow_index < 0 {
19
+ return 1;
20
+ }
21
+ if b.spectator_state == 3 || b.follow_index < 0 {
22
+ return -1;
23
+ }
24
+ if a.connection == 1 {
25
+ return 1;
26
+ }
27
+ if b.connection == 1 {
28
+ return -1;
29
+ }
30
+ let cmp = |x: i32, y: i32| {
31
+ if x < y {
32
+ -1
33
+ } else if x > y {
34
+ 1
35
+ } else {
36
+ 0
37
+ }
38
+ };
39
+ if a.team == 3 && b.team == 3 {
40
+ return cmp(a.spectator_time, b.spectator_time);
41
+ }
42
+ if a.team == 3 {
43
+ return 1;
44
+ }
45
+ if b.team == 3 {
46
+ return -1;
47
+ }
48
+ if mode == 9 {
49
+ return if a.time != 0 && b.time == 0 {
50
+ -1
51
+ } else if a.time == 0 && b.time != 0 {
52
+ 1
53
+ } else {
54
+ cmp(a.time, b.time)
55
+ };
56
+ }
57
+ if mode == 11 && a.class != b.class {
58
+ return -cmp(a.class, b.class);
59
+ }
60
+ if a.score != b.score {
61
+ return -cmp(a.score, b.score);
62
+ }
63
+ if a.deaths != b.deaths {
64
+ return cmp(a.deaths, b.deaths);
65
+ }
66
+ if mode >= 3 && mode != 9 && mode != 11 {
67
+ return -cmp(a.awards, b.awards);
68
+ }
69
+ 0
70
+ }
71
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
72
+ pub enum RankError {
73
+ UnsupportedParticipant,
74
+ InvalidOrder,
75
+ }
76
+ /// Caller supplies a complete ordering. This primitive does not execute a sort
77
+ /// or publication callbacks. Source parity fixtures use eligible n<7 clients.
78
+ pub fn rank_words(
79
+ mode: i32,
80
+ clients: &[Client],
81
+ order: &[usize],
82
+ scores: [i32; 2],
83
+ ) -> Result<Vec<u32>, RankError> {
84
+ if clients
85
+ .iter()
86
+ .any(|c| c.connection != 2 || c.team == 3 || c.spectator_state == 3 || c.follow_index < 0)
87
+ {
88
+ return Err(RankError::UnsupportedParticipant);
89
+ }
90
+ let mut seen = vec![false; clients.len()];
91
+ if order.len() != clients.len() {
92
+ return Err(RankError::InvalidOrder);
93
+ }
94
+ for &id in order {
95
+ if id >= clients.len() || seen[id] {
96
+ return Err(RankError::InvalidOrder);
97
+ }
98
+ seen[id] = true;
99
+ }
100
+ if mode >= 3 && mode != 9 && mode != 11 {
101
+ return Ok(vec![
102
+ if scores[0] == scores[1] {
103
+ 2
104
+ } else if scores[0] > scores[1] {
105
+ 0
106
+ } else {
107
+ 1
108
+ };
109
+ clients.len()
110
+ ]);
111
+ }
112
+ let mut result = vec![0; clients.len()];
113
+ let mut base = 0;
114
+ let mut previous = None;
115
+ for (j, &id) in order.iter().enumerate() {
116
+ let c = clients[id];
117
+ let key = if mode == 9 {
118
+ c.time
119
+ } else if mode == 11 {
120
+ c.class
121
+ } else {
122
+ c.score
123
+ };
124
+ if j == 0 || Some(key) != previous {
125
+ base = j as u32;
126
+ result[id] = base;
127
+ } else {
128
+ result[order[j - 1]] = base | 16384;
129
+ result[id] = base | 16384;
130
+ }
131
+ previous = Some(key);
132
+ }
133
+ Ok(result)
134
+ }
135
+ #[cfg(test)]
136
+ mod tests {
137
+ use super::*;
138
+ fn client() -> Client {
139
+ Client {
140
+ spectator_state: 0,
141
+ follow_index: 0,
142
+ connection: 2,
143
+ team: 1,
144
+ spectator_time: 0,
145
+ time: 0,
146
+ class: 0,
147
+ score: 0,
148
+ deaths: 0,
149
+ awards: 0,
150
+ }
151
+ }
152
+ #[test]
153
+ fn raw_prefilter_quirks_remain_nonordering() {
154
+ let c = Client {
155
+ spectator_state: 3,
156
+ ..client()
157
+ };
158
+ assert_eq!(compare(0, c, c), 1);
159
+ let c = Client {
160
+ connection: 1,
161
+ ..client()
162
+ };
163
+ assert_eq!(compare(0, c, c), 1);
164
+ }
165
+ #[test]
166
+ fn special_modes_and_signed_priorities() {
167
+ let a = Client {
168
+ time: -1,
169
+ class: 2,
170
+ score: i32::MIN,
171
+ awards: i32::MAX,
172
+ ..client()
173
+ };
174
+ let b = Client {
175
+ score: i32::MAX,
176
+ ..client()
177
+ };
178
+ assert_eq!(compare(9, a, b), -1);
179
+ assert_eq!(compare(11, a, b), -1);
180
+ assert_eq!(compare(0, a, b), 1);
181
+ assert_eq!(
182
+ compare(
183
+ 3,
184
+ a,
185
+ Client {
186
+ score: i32::MIN,
187
+ ..b
188
+ }
189
+ ),
190
+ -1
191
+ );
192
+ }
193
+ #[test]
194
+ fn ties_and_global_team_result_words() {
195
+ let c = [
196
+ Client {
197
+ score: 5,
198
+ ..client()
199
+ },
200
+ Client {
201
+ score: 5,
202
+ ..client()
203
+ },
204
+ client(),
205
+ ];
206
+ assert_eq!(
207
+ rank_words(0, &c, &[0, 1, 2], [0, 0]).unwrap(),
208
+ [16384, 16384, 2]
209
+ );
210
+ assert_eq!(rank_words(3, &c, &[0, 1, 2], [1, 1]).unwrap(), [2, 2, 2]);
211
+ }
212
+ #[test]
213
+ fn invalid_permutation_and_participants_reject() {
214
+ let c = [client(), client()];
215
+ assert_eq!(
216
+ rank_words(0, &c, &[0, 0], [0, 0]),
217
+ Err(RankError::InvalidOrder)
218
+ );
219
+ assert_eq!(
220
+ rank_words(0, &c, &[0, 2], [0, 0]),
221
+ Err(RankError::InvalidOrder)
222
+ );
223
+ assert_eq!(
224
+ rank_words(
225
+ 0,
226
+ &[Client {
227
+ team: 3,
228
+ ..client()
229
+ }],
230
+ &[0],
231
+ [0, 0]
232
+ ),
233
+ Err(RankError::UnsupportedParticipant)
234
+ );
235
+ }
236
+ }
docs/ASSET_PIPELINE_TODO.md CHANGED
@@ -982,3 +982,6 @@ Research progress: RE-774 [report](research/QVM_LEADERBOARD_CATEGORIES.md); orig
982
 
983
 
984
  Research progress: RE-775 [report](research/QVM_RECIPIENT_WIRE.md); original assets unchanged.
 
 
 
 
982
 
983
 
984
  Research progress: RE-775 [report](research/QVM_RECIPIENT_WIRE.md); original assets unchanged.
985
+
986
+
987
+ Research progress: RE-776 [report](research/RUST_RANKING.md); original assets unchanged.
docs/FINDINGS.md CHANGED
@@ -1328,3 +1328,6 @@ RE-725–734 batch: [health, contact and Rust impulse findings](research/QVM_HEA
1328
 
1329
 
1330
  **RE-775 — Original recipient command templates and bounded byte serialization model:** Original scoress/scoresd templates carry12 numeric fields plus a name per participant;69338 requests31-byte strncpy then forces the32nd byte to zero. [Evidence](research/QVM_RECIPIENT_WIRE.md).
 
 
 
 
1328
 
1329
 
1330
  **RE-775 — Original recipient command templates and bounded byte serialization model:** Original scoress/scoresd templates carry12 numeric fields plus a name per participant;69338 requests31-byte strncpy then forces the32nd byte to zero. [Evidence](research/QVM_RECIPIENT_WIRE.md).
1331
+
1332
+
1333
+ **RE-776 — Rust raw comparator and eligible rank publication primitives:** Rust raw comparator and rank words match232 comparator and48 eligible complete-ranking source fixtures without imposing Ord on non-ordering prefilters. [Evidence](research/RUST_RANKING.md).
docs/PROVISIONAL_TODO.md CHANGED
@@ -1326,3 +1326,6 @@ Research progress: RE-774 [report](research/QVM_LEADERBOARD_CATEGORIES.md); full
1326
 
1327
 
1328
  Research progress: RE-775 [report](research/QVM_RECIPIENT_WIRE.md); full native/platform/game scope remains open.
 
 
 
 
1326
 
1327
 
1328
  Research progress: RE-775 [report](research/QVM_RECIPIENT_WIRE.md); full native/platform/game scope remains open.
1329
+
1330
+
1331
+ Research progress: RE-776 [report](research/RUST_RANKING.md); full native/platform/game scope remains open.
docs/RESEARCH_LEDGER.md CHANGED
@@ -1533,3 +1533,8 @@ Exactly ten task commits follow baseline `27a7f96f`. Every completed task passed
1533
  ## 2026-10-21 — RE-775 Original recipient command templates and bounded byte serialization model
1534
 
1535
  Original scoress/scoresd templates carry12 numeric fields plus a name per participant;69338 requests31-byte strncpy then forces the32nd byte to zero. Focused `cargo xtask test-group recipient-wire` passes (3 Python tests); selected reports regenerate twice byte-identically, with storage, links and whitespace guards. New report: 120583 bytes. Full-suite validation is deferred to the tenth batch commit. No original assets, Rust runtime or shared VM evaluator changed.
 
 
 
 
 
 
1533
  ## 2026-10-21 — RE-775 Original recipient command templates and bounded byte serialization model
1534
 
1535
  Original scoress/scoresd templates carry12 numeric fields plus a name per participant;69338 requests31-byte strncpy then forces the32nd byte to zero. Focused `cargo xtask test-group recipient-wire` passes (3 Python tests); selected reports regenerate twice byte-identically, with storage, links and whitespace guards. New report: 120583 bytes. Full-suite validation is deferred to the tenth batch commit. No original assets, Rust runtime or shared VM evaluator changed.
1536
+
1537
+
1538
+ ## 2026-10-21 — RE-776 Rust raw comparator and eligible rank publication primitives
1539
+
1540
+ Rust raw comparator and rank words match232 comparator and48 eligible complete-ranking source fixtures without imposing Ord on non-ordering prefilters. Focused `cargo xtask test-group gameplay` passes (34 Python tests); selected reports regenerate twice byte-identically, with storage, links and whitespace guards. New report: 97961 bytes. Full-suite validation is deferred to the tenth batch commit. Rust gameplay crate changed; focused gameplay regenerates eleven parity contracts and scoped package tests. Full suite remains deferred until task ten.
docs/REVERSE_ENGINEERING_TODO.md CHANGED
@@ -1372,3 +1372,6 @@ Purpose: turn the supplied installation into an evidence-backed specification of
1372
 
1373
 
1374
  - [x] RE-775 Original recipient command templates and bounded byte serialization model ([report](research/QVM_RECIPIENT_WIRE.md)).
 
 
 
 
1372
 
1373
 
1374
  - [x] RE-775 Original recipient command templates and bounded byte serialization model ([report](research/QVM_RECIPIENT_WIRE.md)).
1375
+
1376
+
1377
+ - [x] RE-776 Rust raw comparator and eligible rank publication primitives ([report](research/RUST_RANKING.md)).
docs/research/README.md CHANGED
@@ -918,3 +918,6 @@ The extension collector adds five focused tests for native tail calls, command-r
918
 
919
 
920
  [RE-775: Original recipient command templates and bounded byte serialization model](QVM_RECIPIENT_WIRE.md).
 
 
 
 
918
 
919
 
920
  [RE-775: Original recipient command templates and bounded byte serialization model](QVM_RECIPIENT_WIRE.md).
921
+
922
+
923
+ [RE-776: Rust raw comparator and eligible rank publication primitives](RUST_RANKING.md).
docs/research/RUST_RANKING.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Rust raw comparator and eligible rank publication primitives
2
+
3
+ RE-776. Reproduce with `python3 scripts/analyze_rust_ranking.py`; validate with `cargo xtask test-group gameplay`. See the [machine report](rust-ranking.json).
4
+
5
+ Added typed ranking clients, raw source comparator and caller-ordered rank publication with permutation/participant validation. Four Rust unit tests cover prefilter quirks, signed and special-mode priorities, ties/team words and invalid domains. Actual aligned small sorting remains in source evidence; Rust sorting, publication callbacks, larger client domains 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": 42,
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": 46,
2989
  "source_report": "docs/research/qvm-health-impulse.json",
2990
  "source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
2991
  }
docs/research/rust-ranking.json ADDED
The diff for this file is too large to render. See raw diff
 
scripts/analyze_rust_ranking.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Rust raw comparator and eligible rank words against source fixtures."""
3
+ import hashlib,json
4
+ from pathlib import Path
5
+ from qvm_rank_compare_support import FIELDS,DEFAULT
6
+ from rust_lifecycle_support import binary,result
7
+ from analyze_qvm import write_json
8
+
9
+ def words(c):return [c.get(str(o),DEFAULT[o]) for o in FIELDS]
10
+
11
+ def main():
12
+ b=binary();path=Path('docs/research/qvm-rank-comparator.json');raw=path.read_bytes();rank=Path('docs/research/qvm-ranking-complete.json');data=rank.read_bytes();rows=[];ranks=[]
13
+ for p in json.loads(raw)['probes']:
14
+ a=p['input'];rows.append({'input':a,'source':p['result'],'rust':result(b,'rank-compare',[a['mode'],*words(a['first']),*words(a['second'])])})
15
+ for p in json.loads(data)['probes']:
16
+ a=p['input'];args=[a['mode'],*a['team_scores'],len(a['clients']),*p['order'],*[v for c in a['clients'] for v in words(c)]];ranks.append({'input':a,'order':p['order'],'source':p['ranks'],'rust':result(b,'rank-words',args)})
17
+ write_json(Path('docs/research/rust-ranking.json'),{'source_reports':{str(path):hashlib.sha256(raw).hexdigest(),str(rank):hashlib.sha256(data).hexdigest()},'probes':rows,'rank_probes':ranks,'limits':'Rust raw comparator matches232 source cases; rank words match48 eligible n<7 source compositions. No Rust Ord or native sort is claimed. Caller owns order; invalid permutation/unsupported participant rejects. Ranking callbacks, large sort and native/platform/runtime parity remain open.'})
18
+ if __name__=='__main__':main()
scripts/test_rust_ranking.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib,json,unittest
2
+ from pathlib import Path
3
+ from test_qvm_rank_comparator import compare
4
+ class RustRankingTests(unittest.TestCase):
5
+ @classmethod
6
+ def setUpClass(cls):cls.r=json.loads(Path('docs/research/rust-ranking.json').read_text())
7
+ def test_source_raw_compare_and_independent_oracle(self):
8
+ self.assertEqual(len(self.r['probes']),232)
9
+ for p in self.r['probes']:a=p['input'];self.assertEqual(p['rust']['result'],p['source']);self.assertEqual(p['source'],compare(a['mode'],a['first'],a['second']))
10
+ def test_source_rank_words_and_tie_flags(self):
11
+ self.assertEqual(len(self.r['rank_probes']),48)
12
+ for p in self.r['rank_probes']:self.assertEqual(p['rust']['ranks'],p['source'])
13
+ def test_source_identity(self):
14
+ for path,sha in self.r['source_reports'].items():self.assertEqual(hashlib.sha256(Path(path).read_bytes()).hexdigest(),sha)
15
+ if __name__=='__main__':unittest.main()
xtask/checks.tsv CHANGED
@@ -1344,3 +1344,13 @@ group leaderboard-categories scripts/analyze_qvm_leaderboard_categories.py
1344
  report scripts/analyze_qvm_recipient_wire.py docs/research/qvm-recipient-wire.json
1345
  edge docs/research/qvm-recipient-wire.json scripts/test_qvm_recipient_wire.py
1346
  group recipient-wire scripts/analyze_qvm_recipient_wire.py
 
 
 
 
 
 
 
 
 
 
 
1344
  report scripts/analyze_qvm_recipient_wire.py docs/research/qvm-recipient-wire.json
1345
  edge docs/research/qvm-recipient-wire.json scripts/test_qvm_recipient_wire.py
1346
  group recipient-wire scripts/analyze_qvm_recipient_wire.py
1347
+ edge Cargo.toml scripts/analyze_rust_ranking.py
1348
+ edge Cargo.lock scripts/analyze_rust_ranking.py
1349
+ edge crates/urt-gameplay/Cargo.toml scripts/analyze_rust_ranking.py
1350
+ edge crates/urt-gameplay/src/lib.rs scripts/analyze_rust_ranking.py
1351
+ edge crates/urt-gameplay/src/ranking.rs scripts/analyze_rust_ranking.py
1352
+ edge crates/urt-gameplay/src/bin/urt-lifecycle.rs scripts/analyze_rust_ranking.py
1353
+ edge crates/urt-gameplay/src/ranking.rs scripts/analyze_rust_health_impulse.py
1354
+ group gameplay scripts/analyze_rust_ranking.py
1355
+ report scripts/analyze_rust_ranking.py docs/research/rust-ranking.json
1356
+ edge docs/research/rust-ranking.json scripts/test_rust_ranking.py