qvm: compose pre-movement client processing
Browse files- docs/FINDINGS.md +2 -0
- docs/PROVISIONAL_TODO.md +2 -0
- docs/RESEARCH_LEDGER.md +4 -0
- docs/REVERSE_ENGINEERING_TODO.md +2 -0
- docs/research/QVM_CLIENT_PRE_MOVEMENT.md +5 -0
- docs/research/README.md +2 -0
- docs/research/qvm-client-pre-movement.json +0 -0
- scripts/analyze_qvm_client_pre_movement.py +12 -0
- scripts/qvm_client_processing_oracle.py +11 -1
- scripts/qvm_client_processing_support.py +17 -0
- scripts/test_qvm_client_pre_movement.py +12 -0
- xtask/checks.tsv +4 -0
docs/FINDINGS.md
CHANGED
|
@@ -1895,3 +1895,5 @@ RE-979: Rust matches120 original inactivity cases, including signed wrap, pause
|
|
| 1895 |
RE-980: Ninety-six source setup cases recover movement-type precedence, pause-mask1 handling, finite gravity multiplication/truncation and unpaused speed220. A present controller is consumed only on unpaused paths. Packed words use raw shift/OR without byte masking, and the old event sequence is saved. Execution stops at154544 before scratch initialization and later callbacks; actual movement remains open. [Evidence](research/QVM_CLIENT_MOVEMENT_SETUP.md).
|
| 1896 |
|
| 1897 |
RE-981: Rust matches96 original movement-setup cases, preserving type precedence, pause-mask1 behavior, f32 multiplication/truncation, controller consumption and unmasked packed words. Unsupported unpaused float/conversion domains reject atomically; the paused path ignores unused float inputs. Unit tests cover negative truncation, high-bit packing, controller retention and rollback. Actual movement callbacks remain open. [Evidence](research/RUST_CLIENT_MOVEMENT_SETUP.md).
|
|
|
|
|
|
|
|
|
| 1895 |
RE-980: Ninety-six source setup cases recover movement-type precedence, pause-mask1 handling, finite gravity multiplication/truncation and unpaused speed220. A present controller is consumed only on unpaused paths. Packed words use raw shift/OR without byte masking, and the old event sequence is saved. Execution stops at154544 before scratch initialization and later callbacks; actual movement remains open. [Evidence](research/QVM_CLIENT_MOVEMENT_SETUP.md).
|
| 1896 |
|
| 1897 |
RE-981: Rust matches96 original movement-setup cases, preserving type precedence, pause-mask1 behavior, f32 multiplication/truncation, controller consumption and unmasked packed words. Unsupported unpaused float/conversion domains reject atomically; the paused path ignores unused float inputs. Unit tests cover negative truncation, high-bit packing, controller retention and rollback. Actual movement callbacks remain open. [Evidence](research/RUST_CLIENT_MOVEMENT_SETUP.md).
|
| 1898 |
+
|
| 1899 |
+
RE-982: Forty-eight source154089 executions compose preparation, routing, rewritten inputs, actual inactivity and movement setup through154544. Blocking movement can turn an otherwise active command into a timeout; early exits retain preceding timestamp/command mutations. Inactivity receives the pre-round delta, including the allowed negative exception. Native routing/team/send/drop effects remain captured; scratch setup and actual movement are beyond the boundary. [Evidence](research/QVM_CLIENT_PRE_MOVEMENT.md).
|
docs/PROVISIONAL_TODO.md
CHANGED
|
@@ -1949,3 +1949,5 @@ RE-964–973 completed timer deadline/phase/recovery contracts, whole-body compo
|
|
| 1949 |
- [x] RE-980 Original movement parameter selection and packed words ([evidence](research/QVM_CLIENT_MOVEMENT_SETUP.md)).
|
| 1950 |
|
| 1951 |
- [x] RE-981 Rust finite movement parameters and one-shot controller handling ([evidence](research/RUST_CLIENT_MOVEMENT_SETUP.md)).
|
|
|
|
|
|
|
|
|
| 1949 |
- [x] RE-980 Original movement parameter selection and packed words ([evidence](research/QVM_CLIENT_MOVEMENT_SETUP.md)).
|
| 1950 |
|
| 1951 |
- [x] RE-981 Rust finite movement parameters and one-shot controller handling ([evidence](research/RUST_CLIENT_MOVEMENT_SETUP.md)).
|
| 1952 |
+
|
| 1953 |
+
- [x] RE-982 Original pre-movement client pipeline and early-exit state ([evidence](research/QVM_CLIENT_PRE_MOVEMENT.md)).
|
docs/RESEARCH_LEDGER.md
CHANGED
|
@@ -2597,3 +2597,7 @@ Ninety-six source setup cases recover movement-type precedence, pause-mask1 hand
|
|
| 2597 |
## 2026-10-28 — RE-981 Rust finite movement parameters and one-shot controller handling
|
| 2598 |
|
| 2599 |
Rust matches96 original movement-setup cases, preserving type precedence, pause-mask1 behavior, f32 multiplication/truncation, controller consumption and unmasked packed words. Unsupported unpaused float/conversion domains reject atomically; the paused path ignores unused float inputs. Unit tests cover negative truncation, high-bit packing, controller retention and rollback. Actual movement callbacks remain open. Focused `cargo xtask test-group rust-client-movement-setup` 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.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2597 |
## 2026-10-28 — RE-981 Rust finite movement parameters and one-shot controller handling
|
| 2598 |
|
| 2599 |
Rust matches96 original movement-setup cases, preserving type precedence, pause-mask1 behavior, f32 multiplication/truncation, controller consumption and unmasked packed words. Unsupported unpaused float/conversion domains reject atomically; the paused path ignores unused float inputs. Unit tests cover negative truncation, high-bit packing, controller retention and rollback. Actual movement callbacks remain open. Focused `cargo xtask test-group rust-client-movement-setup` 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.
|
| 2600 |
+
|
| 2601 |
+
## 2026-10-28 — RE-982 Original pre-movement client pipeline and early-exit state
|
| 2602 |
+
|
| 2603 |
+
Forty-eight source154089 executions compose preparation, routing, rewritten inputs, actual inactivity and movement setup through154544. Blocking movement can turn an otherwise active command into a timeout; early exits retain preceding timestamp/command mutations. Inactivity receives the pre-round delta, including the allowed negative exception. Native routing/team/send/drop effects remain captured; scratch setup and actual movement are beyond the boundary. Focused `cargo xtask test-group qvm-client-pre-movement` 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
|
@@ -1986,3 +1986,5 @@ RE-964–973 recover the complete bounded timer body and its caller timing prefi
|
|
| 1986 |
- [x] RE-980 Original movement parameter selection and packed words ([evidence](research/QVM_CLIENT_MOVEMENT_SETUP.md)).
|
| 1987 |
|
| 1988 |
- [x] RE-981 Rust finite movement parameters and one-shot controller handling ([evidence](research/RUST_CLIENT_MOVEMENT_SETUP.md)).
|
|
|
|
|
|
|
|
|
| 1986 |
- [x] RE-980 Original movement parameter selection and packed words ([evidence](research/QVM_CLIENT_MOVEMENT_SETUP.md)).
|
| 1987 |
|
| 1988 |
- [x] RE-981 Rust finite movement parameters and one-shot controller handling ([evidence](research/RUST_CLIENT_MOVEMENT_SETUP.md)).
|
| 1989 |
+
|
| 1990 |
+
- [x] RE-982 Original pre-movement client pipeline and early-exit state ([evidence](research/QVM_CLIENT_PRE_MOVEMENT.md)).
|
docs/research/QVM_CLIENT_PRE_MOVEMENT.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Original pre-movement client pipeline and early-exit state
|
| 2 |
+
|
| 3 |
+
RE-982. Focused validation: `cargo xtask test-group qvm-client-pre-movement`. [Machine report](qvm-client-pre-movement.json).
|
| 4 |
+
|
| 5 |
+
Forty-eight source154089 executions compose preparation, routing, rewritten inputs, actual inactivity and movement setup through154544. Blocking movement can turn an otherwise active command into a timeout; early exits retain preceding timestamp/command mutations. Inactivity receives the pre-round delta, including the allowed negative exception. Native routing/team/send/drop effects remain captured; scratch setup and actual movement are beyond the boundary.
|
docs/research/README.md
CHANGED
|
@@ -1521,3 +1521,5 @@ The extension collector adds five focused tests for native tail calls, command-r
|
|
| 1521 |
- [RE-980: Original movement parameter selection and packed words](QVM_CLIENT_MOVEMENT_SETUP.md).
|
| 1522 |
|
| 1523 |
- [RE-981: Rust finite movement parameters and one-shot controller handling](RUST_CLIENT_MOVEMENT_SETUP.md).
|
|
|
|
|
|
|
|
|
| 1521 |
- [RE-980: Original movement parameter selection and packed words](QVM_CLIENT_MOVEMENT_SETUP.md).
|
| 1522 |
|
| 1523 |
- [RE-981: Rust finite movement parameters and one-shot controller handling](RUST_CLIENT_MOVEMENT_SETUP.md).
|
| 1524 |
+
|
| 1525 |
+
- [RE-982: Original pre-movement client pipeline and early-exit state](QVM_CLIENT_PRE_MOVEMENT.md).
|
docs/research/qvm-client-pre-movement.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
scripts/analyze_qvm_client_pre_movement.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Verified original client-processing source contract."""
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from analyze_qvm import write_json
|
| 4 |
+
from qvm_research_support import source
|
| 5 |
+
from qvm_client_processing_support import seed,pipeline,pipeline_fixtures
|
| 6 |
+
|
| 7 |
+
def main():
|
| 8 |
+
q,raw,h,ins=source();probes=[]
|
| 9 |
+
for a in pipeline_fixtures():
|
| 10 |
+
m,b=seed(raw,h,a);m,p,b=pipeline(ins,m,b,a);p['input']=a;probes.append(p)
|
| 11 |
+
write_json(Path('docs/research/qvm-client-pre-movement.json'),dict(source=q['source'],sha256=q['sha256'],probes=probes,limits='48 actual154089 executions through154544 or original early return155716 compose timestamp prep, early routing, command byte rewrites, actual150382 inactivity and movement setup. Rewritten inputs drive activity; earlier timestamp/command mutations persist on ordinary early exits. Routed callbacks/native effects captured; movement scratch initialization and later physics remain open.'))
|
| 12 |
+
if __name__=='__main__':main()
|
scripts/qvm_client_processing_oracle.py
CHANGED
|
@@ -4,7 +4,7 @@ def signed(v):v&=0xffffffff;return v if v<2**31 else v-2**32
|
|
| 4 |
def gate(a):
|
| 5 |
if a['intermission']:return dict(events=[dict(op='intermission',slot=2)],proceed=False)
|
| 6 |
if a['team']==3 or a['follow']:
|
| 7 |
-
return dict(events=[] if a['spectator']==3 else [dict(op='spectator',slot=2,command_time=12345)],proceed=False)
|
| 8 |
return dict(events=[],proceed=True)
|
| 9 |
|
| 10 |
def command(a):
|
|
@@ -37,3 +37,13 @@ def movement(a):
|
|
| 37 |
f32=lambda x:struct.unpack('<f',struct.pack('<f',x))[0]
|
| 38 |
paused=bool(a['pause']&1);gravity=0 if paused else int(f32(f32(a['gravity'])*f32(a['factor']))) if a['controller'] else int(f32(a['gravity']))
|
| 39 |
return dict(pm_type=1 if a['noclip'] else 3 if signed(a['health'])<=0 else 0,gravity_word=gravity&0xffffffff,speed=0 if paused else 220,controller=a['controller'] if paused else 0,packed=[(a['packs'][j]|(a['packs'][j+1]<<8))&0xffffffff for j in [0,2]],event_sequence=a['event_sequence'])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 4 |
def gate(a):
|
| 5 |
if a['intermission']:return dict(events=[dict(op='intermission',slot=2)],proceed=False)
|
| 6 |
if a['team']==3 or a['follow']:
|
| 7 |
+
return dict(events=[] if a['spectator']==3 else [dict(op='spectator',slot=2,command_time=a.get('command_time',12345))],proceed=False)
|
| 8 |
return dict(events=[],proceed=True)
|
| 9 |
|
| 10 |
def command(a):
|
|
|
|
| 37 |
f32=lambda x:struct.unpack('<f',struct.pack('<f',x))[0]
|
| 38 |
paused=bool(a['pause']&1);gravity=0 if paused else int(f32(f32(a['gravity'])*f32(a['factor']))) if a['controller'] else int(f32(a['gravity']))
|
| 39 |
return dict(pm_type=1 if a['noclip'] else 3 if signed(a['health'])<=0 else 0,gravity_word=gravity&0xffffffff,speed=0 if paused else 220,controller=a['controller'] if paused else 0,packed=[(a['packs'][j]|(a['packs'][j+1]<<8))&0xffffffff for j in [0,2]],event_sequence=a['event_sequence'])
|
| 40 |
+
|
| 41 |
+
def pipeline(a):
|
| 42 |
+
from qvm_client_timer_oracle import prepare
|
| 43 |
+
pre=prepare(dict(a,exception=a['spectator']));cmd=dict(angles=list(a['angles']),saved=list(a['saved']),buttons=a['buttons'],move_word=42+sum(v<<(8*(j+1)) for j,v in enumerate(a['moves'])),command_time=pre['command_time']);idle_state=[a['deadline'],a['warned']];view=dict(pm_type=99,gravity_word=111,speed=222,controller=a['controller'],packed=[333,444],event_sequence=None);events=[];idle_result=None;idle_msec=None;done=False
|
| 44 |
+
if pre['proceed']:
|
| 45 |
+
routed=gate(dict(a,command_time=pre['command_time']));events.extend(routed['events'])
|
| 46 |
+
if routed['proceed']:
|
| 47 |
+
cmd=command(dict(a,**cmd,mode=a['game_mode']));moves=[(cmd['move_word']>>(8*(j+1)))&255 for j in range(3)];idle_msec=pre['msec'];idle_out=idle(dict(a,moves=moves,buttons=cmd['buttons'],mode=a['idle_mode'],msec=idle_msec));idle_result=idle_out['accepted'];events.extend(idle_out['events']);idle_state=[idle_out['deadline'],idle_out['warned']]
|
| 48 |
+
if idle_result:view=movement(a);done=True
|
| 49 |
+
return dict(proceed=done,events=events,msec=pre['msec'],idle_result=idle_result,idle_msec=idle_msec,timing=[pre['command_time'],pre['current_time'],pre['copied_tick'],pre['copied_command']],command=cmd,idle_state=idle_state,movement=view,health=a['health'])
|
scripts/qvm_client_processing_support.py
CHANGED
|
@@ -82,3 +82,20 @@ def movement_view(m):return dict(pm_type=m[CLIENT+4]&0xffffffff,gravity_word=m[C
|
|
| 82 |
def movement(ins,m,b,a):
|
| 83 |
movement_seed(m,a);m,e,b=execute(ins,m,b,154405,154544)
|
| 84 |
return m,dict(**movement_view(m),execution={k:e[k] for k in ['stop','trace_length','trace_sha256']}),b
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 82 |
def movement(ins,m,b,a):
|
| 83 |
movement_seed(m,a);m,e,b=execute(ins,m,b,154405,154544)
|
| 84 |
return m,dict(**movement_view(m),execution={k:e[k] for k in ['stop','trace_length','trace_sha256']}),b
|
| 85 |
+
|
| 86 |
+
def pipeline_fixtures():
|
| 87 |
+
defaults=dict(connection=2,spectator=0,command_time=10050,current_time=10000,tick=10000,intermission=0,team=0,follow=0,pause=0,game_mode=0,flags=0,block=0,entity_flags=0,idle_seconds=60,pause_anchor=3000,health=1,exempt=0,idle_mode=0,noclip=0,controller=0,gravity=800.75,factor=0.5,deadline=20000,warned=0,moves=[0,0,0],buttons=0,angles=[11,22,33],saved=[44,55,66],packs=[0x1ff,0x102,0x80000000,0xff0001],event_sequence=0xdeadbeef)
|
| 88 |
+
profiles=[('normal',{}),('disconnected',dict(connection=1)),('stale',dict(command_time=10000)),('negative-exception',dict(command_time=8000,spectator=2,health=0,deadline=9000)),('intermission',dict(intermission=1)),('spectator',dict(team=3)),('spectator-return',dict(team=3,spectator=3)),('follow',dict(follow=1)),('pause-controller',dict(pause=1,controller=1)),('other-pause',dict(pause=2)),('mode10-lock',dict(game_mode=10,flags=16)),('mode10-normal',dict(game_mode=10)),('blocked-timeout',dict(block=1,moves=[1,0,0],deadline=9999)),('movement-saves',dict(moves=[1,0,0],deadline=9999)),('dead',dict(health=0,deadline=9999)),('warning',dict(deadline=19999)),('timeout',dict(deadline=9999)),('spectator-timeout',dict(idle_mode=1,deadline=9999)),('exempt',dict(exempt=1,deadline=0)),('protected',dict(entity_flags=8,deadline=0)),('disabled',dict(idle_seconds=0,warned=1)),('noclip',dict(noclip=1)),('controller',dict(controller=1,factor=-0.5)),('round-nine',dict(command_time=9999,current_time=9990))]
|
| 89 |
+
return [dict(defaults,**changes,name=name,quantize=q) for name,changes in profiles for q in [0,1]]
|
| 90 |
+
|
| 91 |
+
def pipeline(ins,m,b,a):
|
| 92 |
+
idle_seed(m,dict(a,mode=a['idle_mode']));command_seed(m,dict(a,mode=a['game_mode'],move_word=42+sum(v<<(8*(j+1)) for j,v in enumerate(a['moves']))));movement_seed(m,a)
|
| 93 |
+
m.update({34999596:a['intermission'],CLIENT+3096:a['team'],CLIENT+4324:a['follow'],CLIENT+3108:a['spectator'],CLIENT+468:a['connection'],CLIENT+472:a['command_time'],CLIENT:a['current_time'],CLIENT+712:a['quantize'],CLIENT+6172:17,CLIENT+6176:18})
|
| 94 |
+
events=[];idle_result=None;idle_msec=None
|
| 95 |
+
def actual_idle(i,m,args,bs):
|
| 96 |
+
nonlocal idle_result,idle_msec
|
| 97 |
+
assert args[8]==CLIENT
|
| 98 |
+
idle_msec=args[12]&0xffffffff;m,p,b=idle_run(i,m,dict(bs),args[12]);idle_result=p['accepted'];events.extend(p['events']);return m,int(idle_result),{'actual_source_child':150382}
|
| 99 |
+
handlers={**gate_handlers(events),150382:actual_idle};m,e,b=execute(ins,m,b,154090,155717,handlers,(154544,155716));done=e['stop']==154544
|
| 100 |
+
view=dict(pm_type=m[CLIENT+4]&0xffffffff,gravity_word=m[CLIENT+48]&0xffffffff,speed=m[CLIENT+52]&0xffffffff,controller=int(m[ENTITY+852]!=0),packed=[m[CLIENT+288]&0xffffffff,m[CLIENT+292]&0xffffffff],event_sequence=m[ROOT+332]&0xffffffff if done else None)
|
| 101 |
+
p=dict(proceed=done,events=events,msec=None if a['connection']!=2 else m[ROOT+328]&0xffffffff,idle_result=idle_result,idle_msec=idle_msec,timing=[m[CLIENT+o]&0xffffffff for o in [472,0,6172,6176]],command=command_view(m),idle_state=[m[CLIENT+3140]&0xffffffff,m[CLIENT+4144]&0xffffffff],movement=view,health=m[CLIENT+208]&0xffffffff,execution={k:e[k] for k in ['stop','trace_length','trace_sha256']});return m,p,b
|
scripts/test_qvm_client_pre_movement.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json,unittest
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
from qvm_client_processing_oracle import pipeline
|
| 4 |
+
class OriginalTests(unittest.TestCase):
|
| 5 |
+
def test_original_contract(self):
|
| 6 |
+
rows=json.loads(Path('docs/research/qvm-client-pre-movement.json').read_text())['probes'];self.assertEqual(len(rows),48)
|
| 7 |
+
for p in rows:
|
| 8 |
+
for k,v in pipeline(p['input']).items():self.assertEqual(p[k],v)
|
| 9 |
+
def test_fixture_identity_and_trace(self):
|
| 10 |
+
r=json.loads(Path('docs/research/qvm-client-pre-movement.json').read_text());self.assertEqual(len(r['sha256']),64)
|
| 11 |
+
self.assertTrue(all(p['execution']['trace_length']>0 for p in r['probes']))
|
| 12 |
+
if __name__=='__main__':unittest.main()
|
xtask/checks.tsv
CHANGED
|
@@ -3644,3 +3644,7 @@ edge crates/urt-gameplay/src/client_timer.rs scripts/analyze_rust_client_movemen
|
|
| 3644 |
edge crates/urt-gameplay/src/bin/urt-client-timer.rs scripts/analyze_rust_client_movement_setup.py
|
| 3645 |
edge crates/urt-gameplay/src/client_processing.rs scripts/analyze_rust_client_movement_setup.py
|
| 3646 |
edge crates/urt-gameplay/src/bin/urt-client-processing.rs scripts/analyze_rust_client_movement_setup.py
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3644 |
edge crates/urt-gameplay/src/bin/urt-client-timer.rs scripts/analyze_rust_client_movement_setup.py
|
| 3645 |
edge crates/urt-gameplay/src/client_processing.rs scripts/analyze_rust_client_movement_setup.py
|
| 3646 |
edge crates/urt-gameplay/src/bin/urt-client-processing.rs scripts/analyze_rust_client_movement_setup.py
|
| 3647 |
+
report scripts/analyze_qvm_client_pre_movement.py docs/research/qvm-client-pre-movement.json
|
| 3648 |
+
edge docs/research/qvm-client-pre-movement.json scripts/test_qvm_client_pre_movement.py
|
| 3649 |
+
group qvm-client-pre-movement scripts/analyze_qvm_client_pre_movement.py
|
| 3650 |
+
edge scripts/analyze_qvm_client_pre_movement.py scripts/analyze_qvm_resumer_audit.py
|