File size: 7,733 Bytes
e15838a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0113c21
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# Native usercmd retrieval and game-QVM time prefix

RE-405–407 / F-163–165. [qvm-usercmd.json](qvm-usercmd.json) retains verified Linux ELF64 retrieval graphs/trap cases and shipped qagame instruction evidence. Reproduce with `python3 scripts/analyze_qvm_usercmd.py`; verify with `python3 scripts/test_qvm_usercmd.py`.

This extends [server acceptance and selector 7](USERCMD_ACCEPTANCE.md). Original executables, native callees and the complete QVM never execute. VM_ArgPtr results and cross-address-space mapping are supplied. Native retrieval instructions copy bytes, which are explicitly mapped into the synthetic QVM word memory; this is a bounded composition, not a running engine/VM bridge. Calls in QVM entry/wrapper slices are recorded. The time prefix stops before remaining simulation.

## QCMD-001 — Trap 36, index guards and native record copy (RE-405)

The file-backed SV_GameSystemCalls switch slot 36 resolves to dedicated `0x40b7c8` / client-engine `0x473738`. The report verifies the slot's little-endian pointer against the source file and retains its file offset and case instructions. This case first converts argument slot 2 through VM_ArgPtr, then treats slot 1's low 32 bits as a signed client index.

An index below zero or at least the signed sv_maxclients integer reaches `Com_Error(1, format, index)`. The analysis stops at that call and does not assume normal continuation; the native graph's syntactic fallthrough is not an error-recovery contract. Pointer conversion occurs before this guard even on the invalid-index paths. Translation validity, pointer masking/bounds and actual error unwinding remain open.

Valid paths read the client-array base from svs+0x10, multiply the sign-extended index by **0x2df40**, and copy three 64-bit words from client +0x2081c/+0x20824/+0x2082c to the converted destination. The trap case sets return EAX to zero before its common epilogue jump. The standalone `SV_GetUsercmd` performs the same signed guards, explicit stride multiplication and 24-byte copy; it is not called by the inlined trap case.

This independently confirms the earlier arithmetic **candidate stride as an explicit access stride** in these two native paths. Actual array allocation, valid slot lifetime and maximum-client policy still require recovery.

112 probes total (28 standalone and 28 trap cases per engine) cover indices -1/0/1/7/63/64/0x7fffffff against maxima 0/1/8/64. Independent tests verify guards, conversion requests, invalid destination retention, exact 24-byte copies, canaries and valid trap return zero. No Com_Error or VM_ArgPtr callee executes.

## QCMD-002 — Selector 7 wrapper and command placement (RE-406)

The shipped qagame entry dispatch reads jump-table word at VM address 3564 and selects instruction 65 for selector 7. The case loads incoming local 48, forwards it in argument slot 8 and calls helper **155718**, then returns zero. This matches the native selector/client-index boundary recovered previously; the local/table mapping is checked against the source QVM and existing ABI report.

Helper 155718 computes `entity = 34091368 + 880*index`, loads its client pointer from entity +520, and records trap target **-37** (trap 36) with `(index, client_pointer+472)`. Thus the native 24-byte stored usercmd is fetched into the game client record at +472. Entity indexing and pointer validity are supplied, not checked by this wrapper slice.

After retrieval, the helper writes global time word 34992532 into client +4044. It calls helper **154089** with the entity pointer only when `(entity[+424] & 8) == 0` and global word **34084868 == 0**. Bit-8 and global-gate producer semantics remain open; names such as bot/synchronous-client mode require independent confirmation. Retrieval and callback-time storage happen even when those gates suppress the think call.

108 wrapper compositions (54 per engine) cover indices 0/1/63, flag words 0/8/9, gate words 0/1/-1 and time words 1000/0xffffffff. Native copy results become six QVM words under explicitly supplied address mapping. Independent checks verify trap arguments, every copied word, metadata time and exact think-call gating. Native client storage and QVM game-client storage have different bases/layouts; they must remain separate in a Rust port.

## QCMD-003 — Game-side time clamp, elapsed gate and alignment (RE-407)

Helper 154089 begins with a 436-byte QVM frame. The analyzed prefix (154090 through boundary 154210) loads entity +520 and requires client[+468] ==2; other sampled states jump to the return tail at 155716 without this prefix's time mutations.

For state 2, observed operations are sequential:

1. Write game time 34992532 to client +6172.
2. Preserve the raw command time from client +472 at client +6176.
3. Clamp command time downward when it exceeds signed32(game_time+200), then upward when it is below signed32(game_time-1000).
4. Compute signed32(command_time-client[+0]) as elapsed time.
5. If elapsed <1 and client[+3108] !=2, return after the preceding time stores/clamp.
6. Cap elapsed at 200. This modifies local 328, independently of the command timestamp.
7. If client[+712] is nonzero, set command time to signed32(trunc_toward_zero(signed32(command_time+7)/8)*8).

The eight-unit adjustment occurs **after** elapsed calculation and its cap; this prefix does not recompute elapsed or reapply the clamp after rounding. For ordinary positive times this rounds upward to a multiple of eight. Negative/boundary inputs require the recovered signed/truncating expression rather than a generic ceiling formula. Client +0 is provisionally the player-state command time; the +3108 mode and +712 alignment flag still need producer/consumer confirmation.

147 probes cover three rejecting states, 128 ordinary combinations of command time/prior time/mode/alignment, four negative-time rounding fixtures, and twelve signed-boundary combinations near 0x7fffffff/0x80000000. Independent modular arithmetic predicts command time, raw/observed time stores, elapsed and early return. Overflow in the game-time bounds can produce unusual sequential-clamp behavior in synthetic boundary fixtures. These are modeled instruction results, not evidence of legitimate clock wrapping or runtime parity.

The evaluator adds an optional explicit stop-target boundary, allowing state guards and the continuing prefix to stop before the remaining helper. Existing callers keep their original behavior/output. A focused test proves that an unsupported boundary instruction is not executed and still fails when the optional stop is absent.

## Validation and remaining work

Eight tests verify native signed guards/pointer ordering/copy retention, file-backed trap slot and explicit stride, selector/table/argument mapping, wrapper copy/metadata/gates, game-side signed clamps/elapsed/mode/alignment, paired results, source identities and optional stop behavior.

RE-404 remains open for complete callback/producer/allocation/runtime work. RE-408 must confirm the global/record flag meanings, recover command generation and all timestamp/elapsed consumers, continue helper154089 into movement/simulation, characterize selector7 trap/VM mutation effects and address translation, audit other trap36 callers, compose packets through native acceptance/retrieval/game-side processing and validate real traffic/all-platform behavior. Full entity/player/snapshot codecs and Rust/Bevy/Solari implementation remain open.

Follow-up: [think gates](QVM_THINK_GATES.md) continues the timestamp prefix through special routing, command suppression, inactivity decisions and mode-word selection. Gravity/speed, complete movement inputs, action effects and simulation/runtime validation remain open.