|
Download docs/research/ENGINE_CONTROL.md from nmarshall/urt-rs: direct link, hf CLI and curl.
- Browser
- Download file 6.6 kB
-
https://huggingface.co/nmarshall/urt-rs/resolve/main/docs/research/ENGINE_CONTROL.md
- Command line
-
hf download hf://nmarshall/urt-rs/docs/research/ENGINE_CONTROL.md
-
curl -L -o ENGINE_CONTROL.md https://huggingface.co/nmarshall/urt-rs/resolve/main/docs/research/ENGINE_CONTROL.md
6.6 kB
| # Simulation clock, error exits and authentication announcements | |
| Static pass, 2026-10-11. Reproduce with `python3 scripts/analyze_engine_control.py`; validate with `python3 scripts/test_engine_control.py`. [Generated evidence](engine-control.json) contains ten instruction graphs across the two Linux ELF64 engines, selected timing/error instruction windows, the QVM announcement helper and synthetic gate traces. No original game process or complete VM was launched. | |
| ## CTL-001: server simulation time | |
| Both engines' `SV_Frame` functions compute an integer base tick **1000 / sv_fps**, convert it to floating point, multiply by `com_timescale`, then truncate to an integer. A nonpositive result follows a correction path that assigns one. Each simulation step advances the server game-time field at `sv + 0x50850`, places that new value in `%edx`, and invokes `VM_Call(gvm, 8, game_time)`. The QVM frame callback copies its incoming value to global address 34992532, which supplies the deferred-drop comparison. | |
| This establishes millisecond simulation units through the FPS-based tick calculation. The earlier strict elapsed > 100 condition therefore means **more than 100 simulation milliseconds**, checked only when the game frame callback runs. Its wall-clock latency depends on tick scheduling, timescale, pauses and callback order; it is not an independently scheduled 100 ms network timer. | |
| Dedicated VM transfer: `0x412a12`; client-engine equivalent: `0x47a992`. Both occur inside the simulation catch-up loop. `Sys_Milliseconds` independently uses `gettimeofday`, a seconds base, multiplication by 1000 and a compiler reciprocal sequence for microseconds divided by 1000, returning a 32-bit result. This documents the host timing primitive; every event-loop adjustment between host time and frame elapsed time remains untraced. | |
| The server loop also tests `svs + 0x4` against **0x70000000** and has a greater-than branch to `SV_Shutdown` followed by map-command construction. It additionally guards snapshot-counter space. Thus prior synthetic signed-wrap examples describe instruction arithmetic, not proof that a normal uninterrupted server reaches the same wrap state. Exact restart/reset ordering and any bypasses still need fixtures. | |
| ## CTL-002: native error mode 1 | |
| The game error trap invokes `Com_Error` with mode 1. Static branch inspection establishes the following paths in both Linux ELF64 images: | |
| | Condition/path | Observed operations | | |
| | --- | --- | | |
| | Ordinary mode 1, with no escalation or recursion | Format the diagnostic; update an error cvar; print the error; call `SV_Shutdown`; call `CL_Disconnect(1)` and `CL_FlushMemory`; clear loaded-pak state and the error-entered flag; call `longjmp` with value -1. | | |
| | Positive `com_buildScript` value | Replace the requested mode with zero before dispatch. | | |
| | Signed time since previous error ≤ 99 ms | Increment the retained error counter. If the incremented counter is greater than three, replace the mode with zero. Elapsed > 99 resets the counter instead. | | |
| | Error-entered flag already nonzero | Call `Sys_Error` with the recursive-error diagnostic. | | |
| | Mode-zero shutdown path | Shut down client/server, close log/journal files where present and call `Sys_Error`. | | |
| `Com_Frame` establishes `_setjmp` using the frame-abort buffer and branches out when its result is nonzero. `Com_Error` targets the shared buffer through `longjmp`. Consequently the ordinary error path abandons the current frame after shutdown; it is not a normal return to the copy helper. The provided `Sys_Error` functions terminate through `_exit(1)` after diagnostic/terminal cleanup paths. The graph collector retains potential instruction continuation after calls, so nonreturn behavior must be interpreted using these service semantics, not graph reachability alone. | |
| The repeat counter's exact state is important: do not simplify the condition to “the fourth error always exits.” The previous timestamp, counter reset, build-script setting and recursion state alter that outcome. Formatting uses a native `vsprintf` dependency in these bodies; a Rust error boundary will need explicit message ownership, bounded formatting policy and ordered shutdown/frame-abort behavior rather than a direct nonlocal-jump translation. | |
| Windows/macOS parity, repeated-error runtime sequences, subsystem cleanup and all mode values remain RE-225. This pass specifies the observed Linux mode-1 boundary and escalation structure only. | |
| ## CTL-003: begin callback announcement gate | |
| The QVM begin slice at instructions 174979–175026 checks positive global 35017616, handles the pending sentinel/timestamp, and then checks field 3200. With no pending value at least one and field 3200 equal to zero, it sets field 3200 to one and invokes helper **165352** with `(client_record_pointer, 1, 0)`. Disabled/nonpositive global, existing announcement flag or pending drop suppress this call. Synthetic-memory traces verify these gates and forwarded arguments. | |
| Helper 165352 formats authentication/account information, including no-account/disabled/banned/error text, account roles, login, notoriety and level. Its second argument controls a private-message prefix; its body formats print/chat command strings and invokes trap 17 through four observed call sites. These operations identify an authentication status announcement helper; the earlier generic admission/authentication interpretation is narrowed by the actual body. | |
| The third argument and all formatting branches, output-recipient selection, role values, announcement-flag reset/alias writes and interaction with authentication updates remain open. Observing role names in strings does not establish the permission system or server trust policy. The positive-global gate's complete semantics likewise remain provisional. | |
| ## Tooling and verification limits | |
| The control-flow collector records decoded instruction addresses, direct conditional/unconditional edges, return edges, call targets and possible call continuations. Computed jumps remain explicitly unresolved. Tests verify malformed entries/duplicate addresses, branch/call/return edges, unresolved jumps, every local real-graph edge, native shutdown/error/time imports and synthetic announcement gates. These are syntactic graphs and local static checks, not full native decompilation or runtime reference traces. | |
| RE-227–229 record the completed static evidence milestones. Follow up complete frame scheduling/reset behavior, error mode parity and announcement state/output semantics before implementing their Rust counterparts. | |