Download docs/research/qvm/README.md from nmarshall/urt-rs: direct link, hf CLI and curl.
- Browser
- Download file 7.82 kB
-
https://huggingface.co/nmarshall/urt-rs/resolve/main/docs/research/qvm/README.md
- Command line
-
hf download hf://nmarshall/urt-rs/docs/research/qvm/README.md
-
curl -L -o README.md https://huggingface.co/nmarshall/urt-rs/resolve/main/docs/research/qvm/README.md
QVM structure, dispatch and ABI findings
Date: 2026-10-11. All observations refer to the exact member hashes in each module JSON report; source archive identity, member index, original name and extracted path are included. Scripts never execute QVM code. The bounded entry evaluator follows only the recognized prologue and stops at its first jump/call/return.
VM-001: every instruction decoded β observed
| Module | Instructions | Encoded code bytes | Initialized word data | Literal bytes | BSS bytes | Candidate entries | Unresolved calls |
|---|---|---|---|---|---|---|---|
| cgame | 258,563 | 759,988 | 62,940 | 57,304 | 37,935,304 | 1,133 | 231 |
| qagame | 298,900 | 885,716 | 33,364 | 64,188 | 36,445,748 | 1,224 | 89 |
| ui | 101,585 | 303,832 | 31,072 | 32,192 | 9,099,016 | 680 | 199 |
All three use little-endian magic 0x12721444, 32-byte headers and a code offset of 32. Code ends at the declared initialized-data offset. The instruction decoder consumes exactly the declared instruction count and all code except one zero padding byte per module. All immediate conditional-branch destinations are in the instruction table; indices are distinct from byte/file offsets. Initialized data lengths are divisible by four, and data/literal sections end exactly at EOF. BSS is declared memory, not file payload.
The decoder also accepts structurally validated v2 headers (0x12721445) with a byte-sized jump-target section, tested synthetically; no supplied module uses v2. Unknown opcodes, negative lengths/frames/copy sizes, truncated operands, overlapping/out-of-file sections, invalid jump targets and unexpected code tails fail validation. This is a research decoder, not a general compatibility loader: unusual otherwise-supported engine layouts may need deliberate extensions.
Instruction names/order and immediate-width classes were independently checked against ioquake3 revision 83a776283bdb958f82db25554b5ed0966aaf6e49, with seven exact source hashes in the reference manifest. The format declarations, opcode enum, interpreter decoder and loader are pinned in qfiles.h, vm_local.h, vm_interpreted.c, and vm.c. This is source-table validation; shipped interpreter/compiler equivalence remains RE-043.
Operand widths are one byte for the argument-offset instruction, four little-endian signed bytes for constants, locals, frame entry/exit, block-copy sizes and conditional destinations; remaining opcodes have no inline operand. Operand and branch constraints differ by opcode. Integer/float stack semantics, masking, exceptional values and compiled/interpreted parity remain to be specified and measured.
Under the pinned upstream loader's rounding rule, the declared memory sizes imply 64 MiB for cgame and qagame, and 16 MiB for ui, before its extra guard allocation. These are upstream-derived allocation candidates, not measured shipped process allocations or replacement budgets.
VM-002: entry dispatch β observed addresses, inferred names
Each module begins with an entry frame, copies an incoming local slot, checks a nonnegative command against an upper bound, and indexes initialized data to select a code destination. For cgame/qagame, the incoming slot is frame offset 44; for ui it is 36. Treating it as the first command argument follows the upstream entry convention and the observed switch structure. The evaluator initializes that slot only, rejects unknown reads/writes/opcodes, and bounds itself to 128 steps.
| Module | Observed command range | Commands not named by the pinned export enum |
|---|---|---|
| cgame | 0β8 | None |
| qagame | 0β13 | 11, 12, 13 |
| ui | 0β11 | 11 |
The generated *.abi-candidates.json files record each case instruction, VM data-table read, evaluated instruction trace and candidate upstream command name. All valid commands were reproduced from the actual bytes in tests. qagame's command 3 and 7 destinations differ from numeric order, illustrating why code layout cannot stand in for dispatch semantics. Extra command names/return contracts are not guessed. The corresponding native VM-call sites and module bodies must confirm them.
VM-003: trap numbers and unresolved calls β static candidates
| Module | Adjacent constant syscall sites | Distinct numbers | Adjacent constant module call sites | Unresolved call sites |
|---|---|---|---|---|
| cgame | 2,039 | 83 | 4,525 | 231 |
| qagame | 2,217 | 161 | 6,690 | 89 |
| ui | 852 | 71 | 2,117 | 199 |
For a negative constant immediately consumed by a call, the upstream convention gives trap number -1 - target. Direct targets are validated as instruction indices. If a conditional/declared branch can enter the call without executing its preceding constant, the target stays unresolved. Computed incoming edges can remain unknown, so even the remaining adjacent patterns are candidates, not globally proven dataflow. No unresolved site is silently assigned a target.
Every observed number has a site list and upstream import-name candidate where available. A missing enum name is not automatically an Urban Terror extension: qagame uses 100β106 and 111 for shared memory/math helper cases omitted from the selected server import enum. Native case evidence confirms memory copying/filling and several math calls. More significant unmatched numbers are qagame 600, 601, 603 and ui 88, 90, linked to native evidence in the native findings. Even matching numeric names must be checked against the shipped engine's arguments and result handling.
VM-004: string and graph evidence β observed bytes, candidate uses
The extractor finds NUL-terminated printable strings in initialized word/literal regions: 2,538 in cgame, 2,017 in qagame and 1,340 in ui. It records both VM data addresses and file offsets. Constants matching string starts yield 1,534 / 2,211 / 837 candidate reference sites respectively. These can include integer coincidences; substrings and dynamically computed string pointers are not resolved.
*.functions.jsonl partitions code by entry instructions; these are candidate entries, not proven function ends. *.calls.jsonl records each call kind and target. Compressed edge evidence includes immediate branches, linear continuations, adjacent computed-jump candidates and unresolved jumps; it is deliberately not a complete CFG. A call continuation does not prove the callee returns. Stack analysis, dataflow, return edges and reachability remain pending.
Full instructions, edges and strings are deterministic gzip JSONL under assets/analysis/qvm/. For example:
gzip -dc assets/analysis/qvm/qagame.instructions.jsonl.gz | head
gzip -dc assets/analysis/qvm/qagame.strings.jsonl.gz | head
Next questions
- Name qagame commands 11β13 and ui command 11 through native callers and body tracing.
- Recover arguments/results for traps 600/601/603 and ui 88/90, then all remaining trap structures and pointer checks.
- Resolve computed-call targets and jump tables beyond the entry prologues; prove function boundaries and stack balance.
- Build a shipped-engine interpreter/compiler comparison harness before treating pseudocode as gameplay specification.
- Recover movement, weapons, match states, entities and UI behavior from the resulting function/structure map.