nmarshall commited on
Commit
93151ec
·
1 Parent(s): 2a95afe

gameplay: port timer phases and damage requests

Browse files
crates/urt-gameplay/src/bin/urt-client-timer.rs CHANGED
@@ -29,6 +29,50 @@ fn main() {
29
  input.stamp
30
  );
31
  }
 
32
  _ => panic!("unknown timer policy"),
33
  }
34
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  input.stamp
30
  );
31
  }
32
+ "tick" => println!("{}", tick(&w)),
33
  _ => panic!("unknown timer policy"),
34
  }
35
  }
36
+
37
+ fn list(v: &[u32]) -> String {
38
+ format!(
39
+ "[{}]",
40
+ v.iter()
41
+ .map(|v| v.to_string())
42
+ .collect::<Vec<_>>()
43
+ .join(",")
44
+ )
45
+ }
46
+ fn damage_json(v: &[TimerDamage]) -> String {
47
+ v.iter().map(|r|format!(r#"{{"target":{},"inflictor":{},"attacker":{},"direction":{},"point":{},"amount":{},"flags":{},"means":{},"weapon":{}}}"#,r.target,r.inflictor,r.attacker,r.direction,r.point,r.amount,r.flags,r.means,r.weapon)).collect::<Vec<_>>().join(",")
48
+ }
49
+ fn loop_state(w: &[u32]) -> TimerLoopState {
50
+ TimerLoopState {
51
+ flags: w[0],
52
+ status: w[1],
53
+ rate: w[2],
54
+ phase: w[3],
55
+ timer: w[4],
56
+ residual: w[5],
57
+ }
58
+ }
59
+ fn loop_words(s: TimerLoopState) -> [u32; 6] {
60
+ [s.flags, s.status, s.rate, s.phase, s.timer, s.residual]
61
+ }
62
+ fn tick(w: &[u32]) -> String {
63
+ assert_eq!(w.len(), 8);
64
+ let mut s = loop_state(w);
65
+ let input = TimerLoopInput {
66
+ health: w[6],
67
+ weapon: w[7],
68
+ target: 2,
69
+ owner: 3,
70
+ };
71
+ let damage = timer_tick_loop(&mut s, input).unwrap();
72
+ format!(
73
+ r#"{{"damage":[{}],"state":{},"health":{}}}"#,
74
+ damage_json(&damage),
75
+ list(&loop_words(s)),
76
+ w[6]
77
+ )
78
+ }
crates/urt-gameplay/src/client_timer.rs CHANGED
@@ -87,3 +87,132 @@ mod terminal_tests {
87
  assert_eq!(s.entity_flag, 7);
88
  }
89
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  assert_eq!(s.entity_flag, 7);
88
  }
89
  }
90
+
91
+ use crate::native_policy::Error;
92
+ pub const TIMER_DIVISORS: [i32; 6] = [1120403456, 9, 4, 3, 2, 1];
93
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
94
+ pub struct TimerLoopState {
95
+ pub flags: u32,
96
+ pub status: u32,
97
+ pub rate: u32,
98
+ pub phase: u32,
99
+ pub timer: u32,
100
+ pub residual: u32,
101
+ }
102
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
103
+ pub struct TimerLoopInput {
104
+ pub health: u32,
105
+ pub weapon: u32,
106
+ pub target: u32,
107
+ pub owner: u32,
108
+ }
109
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
110
+ pub struct TimerDamage {
111
+ pub target: u32,
112
+ pub inflictor: u32,
113
+ pub attacker: u32,
114
+ pub direction: u32,
115
+ pub point: u32,
116
+ pub amount: u32,
117
+ pub flags: u32,
118
+ pub means: u32,
119
+ pub weapon: u32,
120
+ }
121
+ /// Original150810 loop subset with flag512 disabled. Damage effects are requests;
122
+ /// supplied health is deliberately unchanged. Rate0 aliases the preceding word.
123
+ pub fn timer_tick_loop(
124
+ state: &mut TimerLoopState,
125
+ input: TimerLoopInput,
126
+ ) -> Result<Vec<TimerDamage>, Error> {
127
+ if state.rate > 5 {
128
+ return Err(Error::InvalidIndex);
129
+ }
130
+ if state.flags & 512 != 0 || state.residual as i32 > 10000 {
131
+ return Err(Error::InvalidInput);
132
+ }
133
+ let mut next = *state;
134
+ let mut damage = vec![];
135
+ while next.residual as i32 >= 100 {
136
+ next.phase =
137
+ ((next.phase.wrapping_add(1) as i32) % TIMER_DIVISORS[next.rate as usize]) as u32;
138
+ next.residual = next.residual.wrapping_sub(100);
139
+ if next.flags & 256 != 0 && (input.health as i32) > 0 && next.phase == 0 {
140
+ damage.push(TimerDamage {
141
+ target: input.target,
142
+ inflictor: input.owner,
143
+ attacker: input.owner,
144
+ direction: 0,
145
+ point: 0,
146
+ amount: 1,
147
+ flags: 0,
148
+ means: 23,
149
+ weapon: input.weapon,
150
+ });
151
+ }
152
+ }
153
+ *state = next;
154
+ Ok(damage)
155
+ }
156
+ #[cfg(test)]
157
+ mod tick_tests {
158
+ use super::*;
159
+ fn input() -> TimerLoopInput {
160
+ TimerLoopInput {
161
+ health: 1,
162
+ weapon: 7,
163
+ target: 2,
164
+ owner: 3,
165
+ }
166
+ }
167
+ #[test]
168
+ fn phase_wrap_signed_remainder_alias_and_request_arguments() {
169
+ let mut s = TimerLoopState {
170
+ flags: 256,
171
+ status: 3,
172
+ rate: 0,
173
+ phase: 0,
174
+ timer: 0,
175
+ residual: 250,
176
+ };
177
+ assert!(timer_tick_loop(&mut s, input()).unwrap().is_empty());
178
+ assert_eq!(s.phase, 2);
179
+ assert_eq!(s.residual, 50);
180
+ s.rate = 5;
181
+ s.residual = 100;
182
+ s.phase = i32::MAX as u32;
183
+ let p = timer_tick_loop(&mut s, input()).unwrap();
184
+ assert_eq!(s.phase, 0);
185
+ assert_eq!(
186
+ p,
187
+ [TimerDamage {
188
+ target: 2,
189
+ inflictor: 3,
190
+ attacker: 3,
191
+ direction: 0,
192
+ point: 0,
193
+ amount: 1,
194
+ flags: 0,
195
+ means: 23,
196
+ weapon: 7
197
+ }]
198
+ );
199
+ }
200
+ #[test]
201
+ fn negative_residual_skips_and_unsupported_domains_are_atomic() {
202
+ let mut s = TimerLoopState {
203
+ flags: 256,
204
+ status: 3,
205
+ rate: 2,
206
+ phase: 0,
207
+ timer: 0,
208
+ residual: u32::MAX,
209
+ };
210
+ let old = s;
211
+ assert!(timer_tick_loop(&mut s, input()).unwrap().is_empty());
212
+ assert_eq!(s, old);
213
+ s.rate = 6;
214
+ let old = s;
215
+ assert!(timer_tick_loop(&mut s, input()).is_err());
216
+ assert_eq!(s, old);
217
+ }
218
+ }
docs/FINDINGS.md CHANGED
@@ -1865,3 +1865,5 @@ RE-964: A144-case source contract executes disjoint150791..150810 and151085..151
1865
  RE-965: Rust matches144 original terminal-fragment cases, preserving wrapping cooldown arithmetic before signed comparisons, strict equality behavior, unrelated flag bits and elapsed-total wrap. Separate pre-loop and post-loop APIs allow later composition in original order. Unit tests cover equal/expired deadlines and signed overflow; this contract does not execute the intervening timer loop or native scheduling. [Evidence](research/RUST_CLIENT_TIMER_TERMINAL.md).
1866
 
1867
  RE-966: A288-case source loop contract covers signed residual thresholds, wrapping phase increments and signed remainder with rates0/1/2/5. Rate0 reads preceding word1120403456 instead of a zero divisor. Flag256 plus positive supplied health and phase0 request damage1, means23 and caller weapon/owner pointers. Damage effects remain captured and flag512 is disabled in this stage; health mutation and other branches remain open. [Evidence](research/QVM_CLIENT_TIMER_TICKS.md).
 
 
 
1865
  RE-965: Rust matches144 original terminal-fragment cases, preserving wrapping cooldown arithmetic before signed comparisons, strict equality behavior, unrelated flag bits and elapsed-total wrap. Separate pre-loop and post-loop APIs allow later composition in original order. Unit tests cover equal/expired deadlines and signed overflow; this contract does not execute the intervening timer loop or native scheduling. [Evidence](research/RUST_CLIENT_TIMER_TERMINAL.md).
1866
 
1867
  RE-966: A288-case source loop contract covers signed residual thresholds, wrapping phase increments and signed remainder with rates0/1/2/5. Rate0 reads preceding word1120403456 instead of a zero divisor. Flag256 plus positive supplied health and phase0 request damage1, means23 and caller weapon/owner pointers. Damage effects remain captured and flag512 is disabled in this stage; health mutation and other branches remain open. [Evidence](research/QVM_CLIENT_TIMER_TICKS.md).
1868
+
1869
+ RE-967: Rust matches288 original tick-loop cases with signed residual/remainder and wrapping phase behavior. Rate0 retains the preceding-word divisor1120403456. Typed damage requests retain all source arguments without mutating supplied health. Unit tests cover alias behavior, signed wrap, exact request ABI and atomic invalid-rate rejection. Flag512 is deliberately outside this first loop subset; the residual budget bounds execution. [Evidence](research/RUST_CLIENT_TIMER_TICKS.md).
docs/PROVISIONAL_TODO.md CHANGED
@@ -1917,3 +1917,5 @@ RE-954–963 completed add/remove wrappers, resolved duration export, full-capac
1917
  - [x] RE-965 Rust client timer deadlines and terminal accumulation ([evidence](research/RUST_CLIENT_TIMER_TERMINAL.md)).
1918
 
1919
  - [x] RE-966 Original100ms timer phase loop and damage-request ABI ([evidence](research/QVM_CLIENT_TIMER_TICKS.md)).
 
 
 
1917
  - [x] RE-965 Rust client timer deadlines and terminal accumulation ([evidence](research/RUST_CLIENT_TIMER_TERMINAL.md)).
1918
 
1919
  - [x] RE-966 Original100ms timer phase loop and damage-request ABI ([evidence](research/QVM_CLIENT_TIMER_TICKS.md)).
1920
+
1921
+ - [x] RE-967 Rust100ms phase loop and captured damage requests ([evidence](research/RUST_CLIENT_TIMER_TICKS.md)).
docs/RESEARCH_LEDGER.md CHANGED
@@ -2535,3 +2535,7 @@ Rust matches144 original terminal-fragment cases, preserving wrapping cooldown a
2535
  ## 2026-10-28 — RE-966 Original100ms timer phase loop and damage-request ABI
2536
 
2537
  A288-case source loop contract covers signed residual thresholds, wrapping phase increments and signed remainder with rates0/1/2/5. Rate0 reads preceding word1120403456 instead of a zero divisor. Flag256 plus positive supplied health and phase0 request damage1, means23 and caller weapon/owner pointers. Damage effects remain captured and flag512 is disabled in this stage; health mutation and other branches remain open. Focused `cargo xtask test-group qvm-client-timer-ticks` passes with deterministic report regeneration, relevant regressions and storage/documentation/whitespace checks. Full suite deferred to the tenth-commit boundary. Original assets unchanged; native IO effects remain captured.
 
 
 
 
 
2535
  ## 2026-10-28 — RE-966 Original100ms timer phase loop and damage-request ABI
2536
 
2537
  A288-case source loop contract covers signed residual thresholds, wrapping phase increments and signed remainder with rates0/1/2/5. Rate0 reads preceding word1120403456 instead of a zero divisor. Flag256 plus positive supplied health and phase0 request damage1, means23 and caller weapon/owner pointers. Damage effects remain captured and flag512 is disabled in this stage; health mutation and other branches remain open. Focused `cargo xtask test-group qvm-client-timer-ticks` passes with deterministic report regeneration, relevant regressions and storage/documentation/whitespace checks. Full suite deferred to the tenth-commit boundary. Original assets unchanged; native IO effects remain captured.
2538
+
2539
+ ## 2026-10-28 — RE-967 Rust100ms phase loop and captured damage requests
2540
+
2541
+ Rust matches288 original tick-loop cases with signed residual/remainder and wrapping phase behavior. Rate0 retains the preceding-word divisor1120403456. Typed damage requests retain all source arguments without mutating supplied health. Unit tests cover alias behavior, signed wrap, exact request ABI and atomic invalid-rate rejection. Flag512 is deliberately outside this first loop subset; the residual budget bounds execution. Focused `cargo xtask test-group rust-client-timer-ticks` passes with deterministic report regeneration, relevant regressions and storage/documentation/whitespace checks. Full suite deferred to the tenth-commit boundary. Original assets unchanged; native IO effects remain captured.
docs/REVERSE_ENGINEERING_TODO.md CHANGED
@@ -1954,3 +1954,5 @@ RE-954–963 recover bounded command wrappers, resolved duration export, full-ca
1954
  - [x] RE-965 Rust client timer deadlines and terminal accumulation ([evidence](research/RUST_CLIENT_TIMER_TERMINAL.md)).
1955
 
1956
  - [x] RE-966 Original100ms timer phase loop and damage-request ABI ([evidence](research/QVM_CLIENT_TIMER_TICKS.md)).
 
 
 
1954
  - [x] RE-965 Rust client timer deadlines and terminal accumulation ([evidence](research/RUST_CLIENT_TIMER_TERMINAL.md)).
1955
 
1956
  - [x] RE-966 Original100ms timer phase loop and damage-request ABI ([evidence](research/QVM_CLIENT_TIMER_TICKS.md)).
1957
+
1958
+ - [x] RE-967 Rust100ms phase loop and captured damage requests ([evidence](research/RUST_CLIENT_TIMER_TICKS.md)).
docs/research/README.md CHANGED
@@ -1489,3 +1489,5 @@ The extension collector adds five focused tests for native tail calls, command-r
1489
  - [RE-965: Rust client timer deadlines and terminal accumulation](RUST_CLIENT_TIMER_TERMINAL.md).
1490
 
1491
  - [RE-966: Original100ms timer phase loop and damage-request ABI](QVM_CLIENT_TIMER_TICKS.md).
 
 
 
1489
  - [RE-965: Rust client timer deadlines and terminal accumulation](RUST_CLIENT_TIMER_TERMINAL.md).
1490
 
1491
  - [RE-966: Original100ms timer phase loop and damage-request ABI](QVM_CLIENT_TIMER_TICKS.md).
1492
+
1493
+ - [RE-967: Rust100ms phase loop and captured damage requests](RUST_CLIENT_TIMER_TICKS.md).
docs/research/RUST_CLIENT_TIMER_TICKS.md ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # Rust100ms phase loop and captured damage requests
2
+
3
+ RE-967. Focused validation: `cargo xtask test-group rust-client-timer-ticks`. [Machine report](rust-client-timer-ticks.json).
4
+
5
+ Rust matches288 original tick-loop cases with signed residual/remainder and wrapping phase behavior. Rate0 retains the preceding-word divisor1120403456. Typed damage requests retain all source arguments without mutating supplied health. Unit tests cover alias behavior, signed wrap, exact request ABI and atomic invalid-rate rejection. Flag512 is deliberately outside this first loop subset; the residual budget bounds execution.
docs/research/rust-client-timer-ticks.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": 222,
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": 224,
2989
  "source_report": "docs/research/qvm-health-impulse.json",
2990
  "source_report_sha256": "625fd8064004ea6dbdf398d51d0af844d9c635ff04c0a0790886dddb6ab81e71"
2991
  }
scripts/analyze_rust_client_timer_ticks.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Compare recovered Rust policy with original QVM fixtures."""
2
+ import hashlib,json,os,subprocess
3
+ from pathlib import Path
4
+ from analyze_qvm import write_json
5
+ from rust_lifecycle_support import result
6
+
7
+ def main():
8
+ subprocess.run(['cargo','build','--offline','--package','urt-gameplay','--bin','urt-client-timer'],check=True,capture_output=True)
9
+ binary=Path(os.environ.get('CARGO_TARGET_DIR','target'))/'debug'/'urt-client-timer'
10
+ path=Path('docs/research/qvm-client-timer-ticks.json');data=path.read_bytes();probes=[]
11
+ for p in json.loads(data)['probes']:
12
+ probes.append(dict(source={k:p[k] for k in ['damage', 'state', 'health']},rust=result(binary,'tick',encode(p['input']))))
13
+ write_json(Path('docs/research/rust-client-timer-ticks.json'),dict(source_report=str(path),source_report_sha256=hashlib.sha256(data).hexdigest(),probes=probes,limits='Finite source/Rust contract parity; native time/file/network replies remain supplied or captured.'))
14
+
15
+ def encode(a):
16
+ from analyze_qvm_acceleration import word
17
+ return [a['flags'],a['status'],a['rate'],a['phase'],word(a['timer']),a['residual'],a['health'],a['weapon']]
18
+ if __name__=='__main__':main()
scripts/test_rust_client_timer_ticks.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib,json,unittest
2
+ from pathlib import Path
3
+ class PortTests(unittest.TestCase):
4
+ def test_original_parity(self):
5
+ r=json.loads(Path('docs/research/rust-client-timer-ticks.json').read_text());self.assertGreaterEqual(len(r['probes']),12)
6
+ for p in r['probes']:self.assertEqual(p['source'],p['rust'])
7
+ def test_identity(self):
8
+ r=json.loads(Path('docs/research/rust-client-timer-ticks.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
@@ -3446,3 +3446,23 @@ report scripts/analyze_qvm_client_timer_ticks.py docs/research/qvm-client-timer-
3446
  edge docs/research/qvm-client-timer-ticks.json scripts/test_qvm_client_timer_ticks.py
3447
  group qvm-client-timer-ticks scripts/analyze_qvm_client_timer_ticks.py
3448
  edge scripts/analyze_qvm_client_timer_ticks.py scripts/analyze_qvm_resumer_audit.py
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3446
  edge docs/research/qvm-client-timer-ticks.json scripts/test_qvm_client_timer_ticks.py
3447
  group qvm-client-timer-ticks scripts/analyze_qvm_client_timer_ticks.py
3448
  edge scripts/analyze_qvm_client_timer_ticks.py scripts/analyze_qvm_resumer_audit.py
3449
+ report scripts/analyze_rust_client_timer_ticks.py docs/research/rust-client-timer-ticks.json
3450
+ edge docs/research/rust-client-timer-ticks.json scripts/test_rust_client_timer_ticks.py
3451
+ group rust-client-timer-ticks scripts/analyze_rust_client_timer_ticks.py
3452
+ edge scripts/analyze_rust_client_timer_ticks.py scripts/analyze_qvm_resumer_audit.py
3453
+ edge Cargo.toml scripts/analyze_rust_client_timer_ticks.py
3454
+ edge Cargo.lock scripts/analyze_rust_client_timer_ticks.py
3455
+ edge crates/urt-gameplay/Cargo.toml scripts/analyze_rust_client_timer_ticks.py
3456
+ edge crates/urt-gameplay/src/lib.rs scripts/analyze_rust_client_timer_ticks.py
3457
+ edge crates/urt-gameplay/src/filter_loading.rs scripts/analyze_rust_client_timer_ticks.py
3458
+ edge crates/urt-gameplay/src/filter_lifecycle.rs scripts/analyze_rust_client_timer_ticks.py
3459
+ edge crates/urt-gameplay/src/native_policy.rs scripts/analyze_rust_client_timer_ticks.py
3460
+ edge crates/urt-gameplay/src/bin/urt-filter-loading.rs scripts/analyze_rust_client_timer_ticks.py
3461
+ edge crates/urt-gameplay/src/qvm_format.rs scripts/analyze_rust_client_timer_ticks.py
3462
+ edge docs/research/qvm-client-timer-ticks.json scripts/analyze_rust_client_timer_ticks.py
3463
+ group rust-client-timer-ticks scripts/analyze_rust_health_impulse.py
3464
+ group gameplay scripts/analyze_rust_client_timer_ticks.py
3465
+ edge crates/urt-gameplay/src/filter_commands.rs scripts/analyze_rust_client_timer_ticks.py
3466
+ edge crates/urt-gameplay/src/bin/urt-filter-commands.rs scripts/analyze_rust_client_timer_ticks.py
3467
+ edge crates/urt-gameplay/src/client_timer.rs scripts/analyze_rust_client_timer_ticks.py
3468
+ edge crates/urt-gameplay/src/bin/urt-client-timer.rs scripts/analyze_rust_client_timer_ticks.py