nmarshall commited on
Commit
6d72837
·
1 Parent(s): 2f34f24

gameplay: port staged map reset

Browse files
crates/urt-gameplay/src/bin/urt-transition.rs CHANGED
@@ -147,6 +147,35 @@ fn run() -> Result<String, String> {
147
  map_json(&s)
148
  ))
149
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
  _ => Err("unknown command".into()),
151
  }
152
  }
@@ -209,3 +238,22 @@ fn map_json(s: &MapState) -> String {
209
  .join(",")
210
  )
211
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
147
  map_json(&s)
148
  ))
149
  }
150
+ "reset" => {
151
+ if w.len() < 13 {
152
+ return Err("missing reset header".into());
153
+ }
154
+ let mut s = map_state(&w[..11]);
155
+ let count = w[11] as usize;
156
+ let n = w[12] as usize;
157
+ if n > 64 || w.len() != 13 + 5 * n {
158
+ return Err("invalid reset count".into());
159
+ }
160
+ let mut c = w[13..]
161
+ .chunks_exact(5)
162
+ .map(|v| MapClient {
163
+ score: v[0] as i32,
164
+ field304: v[1] as i32,
165
+ field308: v[2] as i32,
166
+ connection: v[3] as i32,
167
+ class: v[4] as i32,
168
+ })
169
+ .collect::<Vec<_>>();
170
+ prepare_normal(&mut s, &mut c, count).map_err(|e| format!("{e:?}"))?;
171
+ let mid = map_clients_json(&c);
172
+ mark_connecting(&mut c, count).map_err(|e| format!("{e:?}"))?;
173
+ Ok(format!(
174
+ "{{\"state\":{},\"middle\":{mid},\"clients\":{},\"effects\":[122230,7188,249007]}}",
175
+ map_json(&s),
176
+ map_clients_json(&c)
177
+ ))
178
+ }
179
  _ => Err("unknown command".into()),
180
  }
181
  }
 
238
  .join(",")
239
  )
240
  }
241
+
242
+ fn map_clients_json(c: &[MapClient]) -> String {
243
+ format!(
244
+ "[{}]",
245
+ c.iter()
246
+ .map(|c| object(
247
+ &[248, 304, 308, 468, 696],
248
+ &[
249
+ c.score as u32,
250
+ c.field304 as u32,
251
+ c.field308 as u32,
252
+ c.connection as u32,
253
+ c.class as u32
254
+ ]
255
+ ))
256
+ .collect::<Vec<_>>()
257
+ .join(",")
258
+ )
259
+ }
crates/urt-gameplay/src/transition.rs CHANGED
@@ -271,3 +271,103 @@ mod series_tests {
271
  assert_eq!(series(&mut s), SeriesAction::Normal);
272
  }
273
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
  assert_eq!(series(&mut s), SeriesAction::Normal);
272
  }
273
  }
274
+
275
+ #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
276
+ pub struct MapClient {
277
+ pub score: i32,
278
+ pub field304: i32,
279
+ pub field308: i32,
280
+ pub connection: i32,
281
+ pub class: i32,
282
+ }
283
+ fn map_count(clients: &[MapClient], count: usize) -> Result<(), Error> {
284
+ if clients.len() > 64 || count > clients.len() {
285
+ Err(Error::InvalidCount)
286
+ } else {
287
+ Ok(())
288
+ }
289
+ }
290
+ /// Apply after external122230/7188, before external249007. Dispatch remains caller-owned.
291
+ pub fn prepare_normal(
292
+ state: &mut MapState,
293
+ clients: &mut [MapClient],
294
+ count: usize,
295
+ ) -> Result<(), Error> {
296
+ map_count(clients, count)?;
297
+ state.round = 0;
298
+ state.blocked = 0;
299
+ state.scores = [0; 2];
300
+ for c in clients.iter_mut().take(count) {
301
+ if c.connection == 2 {
302
+ c.score = 0;
303
+ if state.mode == 9 {
304
+ c.field304 = 0;
305
+ c.field308 = 0;
306
+ }
307
+ }
308
+ }
309
+ Ok(())
310
+ }
311
+ /// Apply after the map-change child, to its resulting client state.
312
+ pub fn mark_connecting(clients: &mut [MapClient], count: usize) -> Result<(), Error> {
313
+ map_count(clients, count)?;
314
+ for c in clients.iter_mut().take(count) {
315
+ if c.connection == 2 {
316
+ c.connection = 1;
317
+ }
318
+ }
319
+ Ok(())
320
+ }
321
+ #[cfg(test)]
322
+ mod normal_tests {
323
+ use super::*;
324
+ #[test]
325
+ fn staged_clear_preserves_pending_and_unconnected() {
326
+ let mut s = MapState {
327
+ mode: 9,
328
+ pending: 17,
329
+ phase: 2,
330
+ round: 9,
331
+ scores: [5, 7],
332
+ ..Default::default()
333
+ };
334
+ let mut c = [
335
+ MapClient {
336
+ connection: 2,
337
+ score: 8,
338
+ field304: 4,
339
+ field308: 5,
340
+ class: 20,
341
+ },
342
+ MapClient {
343
+ connection: 1,
344
+ score: 6,
345
+ ..Default::default()
346
+ },
347
+ ];
348
+ prepare_normal(&mut s, &mut c, 2).unwrap();
349
+ assert_eq!(
350
+ (c[0].score, c[0].field304, c[0].field308, c[0].connection),
351
+ (0, 0, 0, 2)
352
+ );
353
+ assert_eq!(
354
+ (s.pending, s.phase, s.round, s.scores, c[1].score),
355
+ (17, 2, 0, [0, 0], 6)
356
+ );
357
+ c[0].connection = 0;
358
+ mark_connecting(&mut c, 2).unwrap();
359
+ assert_eq!(c[0].connection, 0);
360
+ }
361
+ #[test]
362
+ fn invalid_normal_count_is_atomic() {
363
+ let mut s = MapState {
364
+ blocked: 7,
365
+ ..Default::default()
366
+ };
367
+ let old = s.clone();
368
+ let mut c = [];
369
+ assert_eq!(prepare_normal(&mut s, &mut c, 1), Err(Error::InvalidCount));
370
+ assert_eq!(s, old);
371
+ assert_eq!(mark_connecting(&mut c, 1), Err(Error::InvalidCount));
372
+ }
373
+ }
docs/FINDINGS.md CHANGED
@@ -1550,3 +1550,6 @@ RE-813–822 replaces the scoreboard decimal assumption with actual37914 executi
1550
 
1551
 
1552
  **RE-850:** 48 original 6939 suffix cases recover round/blocked/global score clears, connected-client score resets, extra mode9 field304/308 resets, then connection2→1 after captured249007. A mid-call snapshot proves ordering; pending, phase, series flags and saved scores remain unchanged. Actual map/command children remain captured. [Evidence](research/QVM_NEXTMAP_RESET.md).
 
 
 
 
1550
 
1551
 
1552
  **RE-850:** 48 original 6939 suffix cases recover round/blocked/global score clears, connected-client score resets, extra mode9 field304/308 resets, then connection2→1 after captured249007. A mid-call snapshot proves ordering; pending, phase, series flags and saved scores remain unchanged. Actual map/command children remain captured. [Evidence](research/QVM_NEXTMAP_RESET.md).
1553
+
1554
+
1555
+ **RE-851:** Rust matches 48 original normal-reset cases with separate pre-map score reset and post-map connection transition APIs. The adapter captures a no-op map child; production callers can apply its real mutations between stages. Safe count errors preserve state, and pending/phase/series data remain unchanged. [Evidence](research/RUST_NEXTMAP_RESET.md).
docs/PROVISIONAL_TODO.md CHANGED
@@ -1569,3 +1569,6 @@ RE-849: Rust matches 300 original series-branch cases, reusing the verified mode
1569
 
1570
 
1571
  RE-850: 48 original 6939 suffix cases recover round/blocked/global score clears, connected-client score resets, extra mode9 field304/308 resets, then connection2→1 after captured249007. A mid-call snapshot proves ordering; pending, phase, series flags and saved scores remain unchanged. Actual map/command children remain captured. Full runtime completion remains open. [Evidence](research/QVM_NEXTMAP_RESET.md).
 
 
 
 
1569
 
1570
 
1571
  RE-850: 48 original 6939 suffix cases recover round/blocked/global score clears, connected-client score resets, extra mode9 field304/308 resets, then connection2→1 after captured249007. A mid-call snapshot proves ordering; pending, phase, series flags and saved scores remain unchanged. Actual map/command children remain captured. Full runtime completion remains open. [Evidence](research/QVM_NEXTMAP_RESET.md).
1572
+
1573
+
1574
+ RE-851: Rust matches 48 original normal-reset cases with separate pre-map score reset and post-map connection transition APIs. The adapter captures a no-op map child; production callers can apply its real mutations between stages. Safe count errors preserve state, and pending/phase/series data remain unchanged. Full runtime completion remains open. [Evidence](research/RUST_NEXTMAP_RESET.md).
docs/RESEARCH_LEDGER.md CHANGED
@@ -1928,3 +1928,8 @@ Rust matches 300 original series-branch cases, reusing the verified mode classif
1928
  ## 2026-10-27 — RE-850 Original normal next-map reset staging and connection changes
1929
 
1930
  48 original 6939 suffix cases recover round/blocked/global score clears, connected-client score resets, extra mode9 field304/308 resets, then connection2→1 after captured249007. A mid-call snapshot proves ordering; pending, phase, series flags and saved scores remain unchanged. Actual map/command children remain captured. Focused `cargo xtask test-group nextmap-reset` 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.
 
 
 
 
 
 
1928
  ## 2026-10-27 — RE-850 Original normal next-map reset staging and connection changes
1929
 
1930
  48 original 6939 suffix cases recover round/blocked/global score clears, connected-client score resets, extra mode9 field304/308 resets, then connection2→1 after captured249007. A mid-call snapshot proves ordering; pending, phase, series flags and saved scores remain unchanged. Actual map/command children remain captured. Focused `cargo xtask test-group nextmap-reset` 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.
1931
+
1932
+
1933
+ ## 2026-10-27 — RE-851 Rust staged normal map reset policy
1934
+
1935
+ Rust matches 48 original normal-reset cases with separate pre-map score reset and post-map connection transition APIs. The adapter captures a no-op map child; production callers can apply its real mutations between stages. Safe count errors preserve state, and pending/phase/series data remain unchanged. Focused `cargo xtask test-group rust-nextmap-reset` passes (9 Python tests); selected reports regenerate twice deterministically, with storage, documentation links and whitespace guards. Full suite deferred to task ten. Original assets unchanged; source fixtures do not establish complete native/runtime parity.
docs/REVERSE_ENGINEERING_TODO.md CHANGED
@@ -1606,3 +1606,6 @@ Purpose: turn the supplied installation into an evidence-backed specification of
1606
 
1607
 
1608
  - [x] RE-850 Original normal next-map reset staging and connection changes ([evidence](research/QVM_NEXTMAP_RESET.md)).
 
 
 
 
1606
 
1607
 
1608
  - [x] RE-850 Original normal next-map reset staging and connection changes ([evidence](research/QVM_NEXTMAP_RESET.md)).
1609
+
1610
+
1611
+ - [x] RE-851 Rust staged normal map reset policy ([evidence](research/RUST_NEXTMAP_RESET.md)).
docs/research/README.md CHANGED
@@ -1144,3 +1144,6 @@ The extension collector adds five focused tests for native tail calls, command-r
1144
 
1145
 
1146
  [RE-850: Original normal next-map reset staging and connection changes](QVM_NEXTMAP_RESET.md).
 
 
 
 
1144
 
1145
 
1146
  [RE-850: Original normal next-map reset staging and connection changes](QVM_NEXTMAP_RESET.md).
1147
+
1148
+
1149
+ [RE-851: Rust staged normal map reset policy](RUST_NEXTMAP_RESET.md).
docs/research/RUST_NEXTMAP_RESET.md ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ # Rust staged normal map reset policy
2
+
3
+ RE-851. Reproduce with `python3 scripts/analyze_rust_nextmap_reset.py`; focused validation: `cargo xtask test-group rust-nextmap-reset`. [Machine report](rust-nextmap-reset.json).
4
+
5
+ Rust matches 48 original normal-reset cases with separate pre-map score reset and post-map connection transition APIs. The adapter captures a no-op map child; production callers can apply its real mutations between stages. Safe count errors preserve state, and pending/phase/series data remain unchanged.
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": 105,
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": 107,
2989
  "source_report": "docs/research/qvm-health-impulse.json",
2990
  "source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
2991
  }
docs/research/rust-nextmap-reset.json ADDED
The diff for this file is too large to render. See raw diff
 
scripts/analyze_rust_nextmap_reset.py ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ import hashlib,json
3
+ from pathlib import Path
4
+ from rust_transition_support import binary,result,invoke
5
+ from analyze_qvm import write_json
6
+ def encode(s):return [s['before'][k] for k in ['phase','option','flags','mode','first','second','blocked','pending','saved_second','saved_first','round']]+[s['input']['max_clients'],len(s['clients_before']),*[c[str(o)] for c in s['clients_before'] for o in [248,304,308,468,696]]]
7
+ def project(s):return {k:s[k] for k in ['state','middle','clients']}|{'effects':[r['target'] for r in s['requests']]}
8
+ def main():
9
+ b=binary();p=Path('docs/research/qvm-nextmap-reset.json');data=p.read_bytes();rows=[]
10
+ for s in json.loads(data)['probes']:rows.append({'source':project(s),'rust':result(b,'reset',encode(s))})
11
+ write_json(Path('docs/research/rust-nextmap-reset.json'),{'source_report':str(p),'source_report_sha256':hashlib.sha256(data).hexdigest(),'probes':rows,'limits':'Rust matches 48 original normal-reset cases with separate pre-map score reset and post-map connection transition APIs. The adapter captures a no-op map child; production callers can apply its real mutations between stages. Safe count errors preserve state, and pending/phase/series data remain unchanged.'})
12
+ if __name__=='__main__':main()
scripts/test_rust_nextmap_reset.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-nextmap-reset.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-nextmap-reset.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
@@ -2303,3 +2303,16 @@ report scripts/analyze_qvm_nextmap_reset.py docs/research/qvm-nextmap-reset.json
2303
  edge docs/research/qvm-nextmap-reset.json scripts/test_qvm_nextmap_reset.py
2304
  group nextmap-reset scripts/analyze_qvm_nextmap_reset.py
2305
  edge scripts/analyze_qvm_nextmap_reset.py scripts/analyze_qvm_resumer_audit.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2303
  edge docs/research/qvm-nextmap-reset.json scripts/test_qvm_nextmap_reset.py
2304
  group nextmap-reset scripts/analyze_qvm_nextmap_reset.py
2305
  edge scripts/analyze_qvm_nextmap_reset.py scripts/analyze_qvm_resumer_audit.py
2306
+ edge Cargo.toml scripts/analyze_rust_nextmap_reset.py
2307
+ edge Cargo.lock scripts/analyze_rust_nextmap_reset.py
2308
+ edge crates/urt-gameplay/Cargo.toml scripts/analyze_rust_nextmap_reset.py
2309
+ edge crates/urt-gameplay/src/lib.rs scripts/analyze_rust_nextmap_reset.py
2310
+ edge crates/urt-gameplay/src/transition.rs scripts/analyze_rust_nextmap_reset.py
2311
+ edge crates/urt-gameplay/src/bin/urt-transition.rs scripts/analyze_rust_nextmap_reset.py
2312
+ edge docs/research/qvm-nextmap-reset.json scripts/analyze_rust_nextmap_reset.py
2313
+ group rust-nextmap-reset scripts/analyze_rust_health_impulse.py
2314
+ group gameplay scripts/analyze_rust_nextmap_reset.py
2315
+ report scripts/analyze_rust_nextmap_reset.py docs/research/rust-nextmap-reset.json
2316
+ edge docs/research/rust-nextmap-reset.json scripts/test_rust_nextmap_reset.py
2317
+ group rust-nextmap-reset scripts/analyze_rust_nextmap_reset.py
2318
+ edge scripts/analyze_rust_nextmap_reset.py scripts/analyze_qvm_resumer_audit.py