urt-rs / docs /research /ENGINE_CONTROL.md
nmarshall's picture
Trace simulation timing error exits and authentication announcements
63e6366
|
Raw History Blame Contribute Delete
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 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.