Download docs/research/FRAME_TIMING.md from nmarshall/urt-rs: direct link, hf CLI and curl.
- Browser
- Download file 8.41 kB
-
https://huggingface.co/nmarshall/urt-rs/resolve/main/docs/research/FRAME_TIMING.md
- Command line
-
hf download hf://nmarshall/urt-rs/docs/research/FRAME_TIMING.md
-
curl -L -o FRAME_TIMING.md https://huggingface.co/nmarshall/urt-rs/resolve/main/docs/research/FRAME_TIMING.md
Event timestamps, frame elapsed time and pause gates
Static pass, 2026-10-11. Reproduce with python3 scripts/analyze_frame_timing.py; validate with python3 scripts/test_frame_timing.py. Generated evidence records ten instruction graphs, file-backed constants, two seven-entry event tables, direct pause cvar assignments, frame/pause instruction windows and 64 bounded synthetic native instruction traces. Inputs are verified against the original and disassembly SHA-256 identities before analysis. No original process or complete VM is launched.
TIME-001: elapsed-time modification
Both Linux ELF64 images contain Com_ModifyMsec (dedicated 0x42d4de, client 0x43df8a) and equivalent inlined calculations in Com_Frame. The memory float compared with timescale is 0.0, verified from the actual file-backed bytes 00000000; a nearby symbol name does not identify that constant.
For finite, in-range values, the observed helper selects milliseconds in this order:
- A nonzero
com_fixedtimeinteger replaces incoming elapsed time, bypassing multiplication. - Otherwise a nonzero timescale multiplies elapsed time using single-precision conversion/multiplication and truncation toward zero.
- Otherwise a nonzero camera-mode integer keeps the zero candidate; with camera mode zero, incoming elapsed time is used.
- A nonpositive candidate is replaced with one if timescale is nonzero. With zero timescale that correction is skipped. The zero-timescale/camera path also bypasses the nonpositive test with its zero candidate.
- Apply the upper bound according to the table below.
| Dedicated cvar | Local server running | Upper bound | Diagnostic |
|---|---|---|---|
| Nonzero | Either | 5000 ms | With a running server, a candidate strictly greater than 500 ms is passed to Com_Printf before clamping. |
| Zero | Nonzero | 200 ms | No dedicated-hitch diagnostic in this path. |
| Zero | Zero | 5000 ms | No dedicated-hitch diagnostic in this path. |
The table depends on cvars, rather than simply the executable filename. Representative traces in both images produce: elapsed 201 → 200 for a local nondedicated server; elapsed 5001 → 5000 when no local server runs; dedicated/running 500 → 500 with no print, 501 → 501 with one print. Timescale 0 with elapsed 16 and camera mode 0 produces 16; setting camera mode 1 produces 0. Fixed time -5 with zero timescale produces -5, whereas with timescale 1 it becomes 1. Fixed time 17 with timescale 2 remains 17. These branches make “timescale zero always pauses” an incorrect replacement contract for this helper.
Com_Frame stores the raw measured interval in com_frameMsec before the inlined modification. It sends the resulting interval to SV_Frame; on its nondedicated branch, it pumps events and executes buffered commands again before CL_Frame. The earlier simulation-tick calculation separately multiplies the FPS-derived tick by timescale. Do not merge the raw host interval, modified frame interval and authoritative simulation clock into one Rust clock.
The evaluator executes only decoded instructions from Com_ModifyMsec, with symbolic cvar pointers and synthetic values. It rounds scalar operations to float32, follows the actual conditional targets and records diagnostic calls without entering callees. It rejects unknown opcodes/operands, nonfinite timescale and out-of-range float-to-int conversion. It does not model SSE exceptions, arbitrary memory, ABI execution, the full inlined frame body or reachable cvar constraints. Negative fixed time is a synthetic boundary example, not a claim about normal gameplay settings.
TIME-002: event timestamp source and dispatch
| Function | Dedicated address | Client address |
|---|---|---|
Sys_GetEvent |
0x4a7308 |
0x555d88 |
Com_GetRealEvent |
0x42ee3e |
0x43fba4 |
Com_EventLoop |
0x42f1de |
0x43ff42 |
Sys_GetEvent first consumes an already queued event if available. Otherwise it polls key events, console input, input-frame processing and network packets. If no event becomes available, it zeroes a 32-byte event record and places Sys_Milliseconds() at offset zero. Com_GetRealEvent normally delegates to that provider; journal mode 2 instead reads a 32-byte event record and optional payload, while mode 1 writes the record and payload. Short record/payload I/O routes to Com_Error. Journal paths therefore prevent an assumption that every frame timestamp comes directly from a fresh host-clock sample.
Com_EventLoop consumes its pushed-event ring before calling Com_GetRealEvent. For type zero it drains client and server loopback packet queues, then returns the event timestamp. Nonzero types dispatch through the file-backed tables at dedicated 0x4b3ea8 and client 0x56ba10:
| Type | Observed service/path |
|---|---|
| 0 | Table entry points to payload cleanup, but the earlier type-zero branch bypasses this switch and handles loopback/return. |
| 1 | CL_KeyEvent, with event time forwarded. |
| 2 | CL_CharEvent. |
| 3 | CL_MouseEvent, with event time forwarded. |
| 4 | CL_JoystickEvent, with event time forwarded. |
| 5 | Cbuf_AddText calls for console input and a second string. |
| 6 | Packet handling with com_dropsim, payload length checks and server/client routing. |
The collector reads all 56 table bytes from their mapped file offsets and verifies every target is an instruction in the function. Linear entry windows can span other cases and are not standalone case executions. Out-of-range event types route to Com_Error; payload cleanup calls Z_Free on the ordinary handled-event paths. Detailed packet/drop simulation and queue overflow/ownership behavior remain follow-up work.
At the first frame event pump, Com_Frame compares the returned timestamp with retained lastTime using a signed comparison. A smaller timestamp resets lastTime to that value before subtraction. It pumps again until the elapsed value meets the selected minimum interval. The ordinary minimum is one millisecond; the nondedicated, positive-maxfps, non-timedemo path uses integer 1000 / com_maxfps. Raw elapsed is recorded before the modification described above. This is bounded static ordering evidence, not a complete wraparound or scheduling specification.
TIME-003: pause state and inline gate
SV_CheckPaused appears at dedicated 0x41035a and client 0x4782da. With cl_paused zero, it returns zero directly. Otherwise it scans native client records with stride 0x2df40, admitting a record into its count when the state word is unsigned greater than one and the word at 0x24ee4 is unsigned at least one. The second predicate follows from cmp 1 and sbb -1; its semantic field name remains unresolved here. Do not call this a human-client or bot filter without recovering that layout.
With count at most one, the helper sets sv_paused to string "1" if needed and returns one. With count greater than one, it sets sv_paused to "0" if needed and returns zero. Both direct Cvar_Set argument pairs are decoded from the original bytes and included in the report. If cl_paused is zero, this helper does not clear a previously set sv_paused value.
SV_Frame contains an equivalent inlined gate before its tick calculation and game callback loop: the pause path returns without running those simulation steps. This is server-engine pause evidence, not the game-mode timeout implementation. Full paused networking, accumulated-time behavior, client presentation, game timeout rules and all-platform parity remain unverified.
Completion and follow-up
RE-232–234 cover these bounded static deliverables. RE-231 and PROV-001 remain open because complete catch-up, restart, callback ordering, runtime constraints and Windows/macOS/ELF32 behavior have not been recovered. Next recover the counted native field, validate actual configurations and journal fixtures, and compare the complete host-frame/server-frame paths across platforms. The Rust scheduling contract must preserve observed clock distinctions and explicitly address unsupported numeric domains after further evidence.
Follow-up: EVT-001 resolves field 0x24ee4 to the native netchannel address type and identifies zero-type bots. The raw predicate and runtime limitations above remain applicable.