United Offensive network tuning =============================== .. meta:: :description: Source-derived guidance for selecting Call of Duty: United Offensive client and server network settings, including packet, frame, snapshot, rate, and server-tick interactions. :keywords: call of duty, united offensive, cod uo, network settings, cl_maxpackets, cl_packetdup, rate, snaps, sv_fps, sv_maxRate, com_maxfps Scope and authority ------------------- This guide explains how to tune the stock Call of Duty: United Offensive 1.51 multiplayer client and server. Its behavioral claims and numeric limits are derived exclusively from the maintained ``STRICT_STOCK`` source. No settings from later engines, community patches, or the recovery's compatibility changes are assumed. The source defines scheduling behavior, but it cannot define the capacity, loss, jitter, or CPU load of a particular installation. The recommendations below are engineering conclusions from that behavior. They distinguish values which minimize delay from values which conserve bandwidth or tolerate loss. This page is a tuning companion to the :doc:`../specs/network_protocol` wire-format specification and the complete :doc:`../refs/console_uo/cvars` registry. Executive recommendations ------------------------- There is no single best value for every connection. For a stable broadband client, this is a coherent balanced profile: .. code-block:: text seta com_maxfps 125 seta cl_maxpackets 62 seta cl_packetdup 1 seta rate 50000 seta snaps 30 set cl_timeNudge 0 set cl_nodelta 0 set cg_nopredict 0 At an ideal 8 ms client-frame cadence, ``cl_maxpackets 62`` has a 16 ms packet threshold and therefore sends on every second frame: approximately 62.5 client packets per second. ``rate 50000`` is high enough that even the largest packet size charged by the stock snapshot scheduler does not rate-delay a 30-snapshot request. ``snaps 30`` requests the server-side maximum, but a stock ``sv_fps 20`` server can still produce only about 20 snapshots per second. Use a different profile when its tradeoff is intentional: .. list-table:: Suggested client profiles :header-rows: 1 :widths: 18 14 14 12 10 32 * - Purpose - ``com_maxfps`` - ``cl_maxpackets`` - ``rate`` - ``snaps`` - Tradeoff * - Stock baseline - ``85`` - ``30`` - ``25000`` - ``20`` - Conservative bandwidth use; already aligned with the stock 20 Hz server. * - Balanced broadband - ``125`` - ``62`` - ``50000`` - ``30`` - About 62.5 outgoing packets/s with enough receive allowance for the worst scheduler-accounted packet at 30 snapshots/s. * - Minimum command batching - ``100`` - ``100`` - ``50000`` or more - ``30`` - One packet opportunity per 10 ms client frame; more packet overhead and server packet-processing work. * - Constrained connection - ``85`` - ``30`` - ``25000`` - ``20`` - Fewer datagrams and a lower server-to-client allowance; large snapshots can be rate-delayed. Keep ``cl_packetdup 1`` for these profiles. Try ``0`` only on a demonstrably clean path when minimizing uplink bytes matters. Try ``2`` when actual packet loss is visible. Increasing it does not reduce ping or snapshot jitter. For a server, choose one of the two deliberate tick/snapshot models: .. list-table:: Suggested server profiles :header-rows: 1 :widths: 18 12 18 22 30 * - Purpose - ``sv_fps`` - Client ``snaps`` - ``sv_maxRate`` - Tradeoff * - Stock-aligned - ``20`` - ``20`` or ``30`` - ``0`` or a planned cap - 50 ms game frames and at most about 20 snapshots/s. ``snaps 30`` gives no extra snapshots here. * - Higher update rate - ``30`` - ``30`` - At least ``50000`` when bandwidth permits - 33 ms integer game frames and about 30.3 snapshot opportunities/s; greater CPU and uplink demand. ``sv_maxRate 0`` means that the server imposes no additional cap. Stock clients are still clamped to a maximum requested ``rate`` of 90000. A hosted server should use a nonzero cap when the aggregate uplink budget requires one. Stock defaults and enforced domains ----------------------------------- .. list-table:: Relevant stock cvars :header-rows: 1 :widths: 20 14 25 41 * - Cvar - Stock default - Effective domain - What it controls * - ``com_maxfps`` - ``85`` - Positive values use ``1000 / value`` milliseconds - The client main-loop frame gate. One user command and at most one normal packet opportunity are produced per rendered client frame. * - ``cl_maxpackets`` - ``30`` - Clamped to ``15..100`` - Minimum elapsed time between normal client packets to a non-LAN server. * - ``cl_packetdup`` - ``1`` - Clamped to ``0..5`` - How far back in sent-packet history the next packet reaches when selecting user commands to include. * - ``rate`` - ``25000`` - Server clamps to ``1000..90000`` bytes/s - The client's requested server-to-client scheduling allowance. It does not cap client FPS or client packet rate. * - ``snaps`` - ``20`` - Server clamps to ``1..30`` - The client's requested snapshot interval, stored as ``1000 / snaps`` integer milliseconds. * - ``cl_timeNudge`` - ``0`` - Runtime clamp ``-30..30`` ms - Moves client presentation time relative to the synchronized server timeline. * - ``cl_nodelta`` - ``0`` - Boolean use - Requests full rather than delta-compressed server snapshots. * - ``cg_nopredict`` - ``0`` - Boolean use - Disables local movement prediction and uses interpolation. * - ``cg_predictItems`` - ``1`` - Boolean use - Enables local item-pickup prediction; it is also sent in userinfo. * - ``sv_fps`` - ``20`` - Values below ``1`` are replaced with ``10`` - Integer server-frame quantum and game-module update cadence. * - ``sv_maxRate`` - ``0`` - ``0`` is no server cap; a nonzero value below ``1000`` is raised to ``1000`` - Caps each remote client's effective server-to-client rate. None of these stock tuning cvars carries ``CVAR_CHEAT``. The archive flag is separate from cheat protection: ``rate``, ``snaps``, ``cl_maxpackets``, ``cl_packetdup``, and ``com_maxfps`` are archived, whereas such diagnostic or runtime controls as ``cl_timeNudge`` are not. The stock multiplayer menu applies only coarse connection presets. When its ``ui_setRate`` action sees ``rate >= 5000``, it selects ``cl_maxpackets 30`` and ``cl_packetdup 1``. From ``4000`` through ``4999`` it selects ``15`` and ``2``; below ``4000`` it selects ``15`` and ``1``. These are bandwidth-era presets, not evidence that 30 packets/s is an engine limit. The four independent clocks --------------------------- Tuning is easier when the four rates are kept separate: 1. **Client render/user-command rate.** ``CL_CreateNewCommands`` creates one user command each active client frame. 2. **Client packet rate.** ``CL_ReadyToSendPacket`` can send only when a client frame calls it and the ``cl_maxpackets`` elapsed-time threshold has passed. 3. **Server game rate.** ``SV_Frame`` runs the game VM in quanta of ``1000 / sv_fps`` integer milliseconds. 4. **Server snapshot rate.** ``SV_SendClientMessages`` considers a client only after a server frame and only when that client's snapshot/rate timer is due. Therefore: * ``com_maxfps`` does not set packet rate directly; * ``cl_maxpackets`` does not set snapshot rate; * ``snaps`` does not set the authoritative game tick; * ``sv_fps`` does not directly cap incoming client packets; and * matching all four numeric values is neither required nor generally possible. Client FPS and ``cl_maxpackets`` -------------------------------- The stock client performs both limits with integer millisecond division: .. code-block:: text client frame threshold = floor(1000 / com_maxfps) ms packet threshold = floor(1000 / cl_maxpackets) ms The packet test runs once per client frame. If a stable client frame period is ``F`` milliseconds and the packet threshold is ``P``, the idealized packet cadence is: .. code-block:: text frames per packet = ceil(P / F) packet interval = frames per packet * F packets per second ~= 1000 / packet interval Actual rendering load, timer granularity, and frame-time jitter can only alter the observed result. The formula explains why raising ``cl_maxpackets`` can produce no change: a packet still waits for the next rendered frame. .. list-table:: Idealized stock packet-rate examples :header-rows: 1 :widths: 15 16 18 18 16 17 * - ``com_maxfps`` - Frame threshold - ``cl_maxpackets`` - Packet threshold - Frames/packet - Approx. packets/s * - ``85`` - 11 ms - ``30`` - 33 ms - 3 - 30.3 * - ``85`` - 11 ms - ``100`` - 10 ms - 1 - 90.9 * - ``100`` - 10 ms - ``100`` - 10 ms - 1 - 100 * - ``125`` - 8 ms - ``30`` - 33 ms - 5 - 25 * - ``125`` - 8 ms - ``62`` - 16 ms - 2 - 62.5 * - ``125`` - 8 ms - ``100`` - 10 ms - 2 - 62.5 * - ``200`` - 5 ms - ``100`` - 10 ms - 2 - 100 * - ``250`` - 4 ms - ``83`` - 12 ms - 3 - 83.3 * - ``250`` - 4 ms - ``100`` - 10 ms - 3 - 83.3 Two unintuitive consequences are important: * At a stable 125 FPS, changing ``cl_maxpackets`` from 62 to 100 does not create 100 packet opportunities per second; both thresholds are first met on the second 8 ms frame. * At a stable 250 FPS, the stock maximum ``cl_maxpackets 100`` is first met on the third 4 ms frame, yielding about 83.3 packets/s rather than 100. Choose a client FPS cap the machine can sustain, then choose the packet cadence. Do not select a higher ``cl_maxpackets`` merely because its number looks closer to the FPS cap. A stable aligned cadence is more predictable than a nominally higher value crossed irregularly by frame boundaries. The dedicated-server tick is not an additional gate inside ``CL_ReadyToSendPacket``. The server processes a valid packet when its network event is dispatched and passes each newer bundled user command to ``GAME_CLIENT_THINK``. Sending faster than ``sv_fps`` can therefore reduce the time commands wait on the client, even though authoritative game frames and outgoing snapshots still occur at the server cadence. Conversely, matching ``cl_maxpackets 20`` to ``sv_fps 20`` is not required: packets can bundle more than one user command. LAN exception ~~~~~~~~~~~~~ For a loopback or LAN address, the stock client bypasses the ``cl_maxpackets`` threshold and permits a packet on every client frame. The server also bypasses its remote rate/snapshot timer for LAN addresses and makes the client due again immediately. Delivery is still considered only when the server reaches ``SV_SendClientMessages``, so the server frame remains the snapshot ceiling. ``cl_packetdup`` and command redundancy --------------------------------------- ``cl_packetdup`` does not transmit identical copies of a UDP datagram. Each new client packet chooses its user-command starting point from an older outgoing-packet history entry: .. code-block:: text history sequence = outgoing sequence - cl_packetdup - 1 With ``cl_packetdup 0``, the packet includes commands created since the immediately preceding packet. With ``1``, it reaches one packet farther back, so recently sent commands are carried again. Values through ``5`` increase that overlap. Reliable text commands use their own acknowledgement/retry window and are not the reason to raise ``cl_packetdup``. The movement command section has a hard limit of 32 commands per packet. A useful approximation is: .. code-block:: text commands selected per packet ~= (1 + cl_packetdup) * rendered FPS / actual client packets per second Keep that result comfortably below 32. High FPS combined with a low packet rate and a large duplication value reaches the cap; the client then prints ``MAX_PACKET_USERCMDS`` and retains only the newest 32-command window. More duplication is therefore not unconditionally safer. Recommended use: * ``1`` is the balanced stock default and should remain the starting point; * ``0`` reduces redundant movement data on a clean, bandwidth-constrained uplink; * ``2`` can cover a wider command history when packet loss is observed; and * values ``3..5`` should be reserved for diagnosed loss after checking the 32-command budget. ``rate`` and snapshot bandwidth ------------------------------- ``rate`` is a byte-per-second request sent in userinfo. For an Internet client, the server clamps it to ``1000..90000`` and then applies ``sv_maxRate`` when that cvar is nonzero and lower. Define: .. code-block:: text effective rate = min(client rate, nonzero sv_maxRate) The server's snapshot scheduler charges 48 bytes of packet overhead and caps the message-size term at 1500 bytes: .. code-block:: text charged size = min(compressed packet size, 1500) + 48 rate delay = floor(charged size * 1000 / effective rate) ms next delay = max(rate delay, client snapshot interval) The due time is subsequently observed only on a server frame. ``rate`` is therefore an allowance, not a guaranteed usage level and not a packet count. Raising it does not lower propagation latency or repair packet loss. It only prevents the server's byte scheduler from spacing packets more widely and also permits faster stock in-band downloads. For the largest 1500-byte size charged by the scheduler: .. list-table:: Maximum-accounted-packet rate delay :header-rows: 1 :widths: 20 22 29 29 * - Effective ``rate`` - Computed delay - Result on 20 Hz server - Result on 30 Hz server * - ``25000`` - 61 ms - Quantized to 100 ms, about 10 snapshots/s - Quantized to 66 ms, about 15.2 snapshots/s * - ``30000`` - 51 ms - Quantized to 100 ms, about 10 snapshots/s - Quantized to 66 ms, about 15.2 snapshots/s * - ``31000`` - 49 ms - 50 ms, about 20 snapshots/s - Quantized to 66 ms, about 15.2 snapshots/s * - ``50000`` - 30 ms - 50 ms, about 20 snapshots/s - 33 ms, about 30.3 snapshots/s * - ``90000`` - 17 ms - 50 ms, about 20 snapshots/s - 33 ms, about 30.3 snapshots/s This table is a worst case for scheduling because ordinary delta snapshots can be smaller. At ``rate 25000``, a packet up to roughly 1201 charged message bytes avoids slowing a 50 ms request, while only roughly 776 message bytes fit without slowing a 33 ms request. Thus the stock ``25000`` default can be adequate for ordinary 20 Hz play but cannot guarantee that a large snapshot will avoid rate delay. Use ``rate 50000`` when the path can sustain it and the goal is unthrottled 30-snapshot gameplay. Use ``90000`` when maximizing the permitted in-band transfer/fragment pace is also useful. Retain or lower ``25000`` when the connection or server budget requires it; requesting bytes the path cannot carry merely moves loss and queueing outside the engine. Server operators should budget ``sv_maxRate`` per active client. As a conservative allowance calculation, 20 clients at 50000 bytes/s represent 1,000,000 bytes/s (about 8 Mbit/s) before additional transport and connectionless traffic. Setting ``sv_maxRate 0`` allows each stock client to request as much as 90000 bytes/s; a nonzero cap should reflect the server's real sustained uplink rather than a desired ping value. ``snaps`` and ``sv_fps`` -------------------------- The server converts the userinfo ``snaps`` value to an integer interval after clamping it to ``1..30``: .. code-block:: text snapshot interval = floor(1000 / snaps) ms It also converts ``sv_fps`` to a server-frame quantum: .. code-block:: text server frame = floor(1000 / sv_fps) ms Snapshot due times are checked only after a server frame. Ignoring rate delay, the idealized remote-client result is therefore: .. code-block:: text actual snapshot interval = ceil(snapshot interval / server frame) * server frame When rate scheduling is active, replace ``snapshot interval`` in that formula with the larger of the snapshot interval and the computed rate delay. .. list-table:: Snapshot/server-frame alignment examples :header-rows: 1 :widths: 14 17 12 19 18 20 * - ``sv_fps`` - Server frame - ``snaps`` - Requested interval - Actual interval - Approx. snapshots/s * - ``20`` - 50 ms - ``20`` - 50 ms - 50 ms - 20 * - ``20`` - 50 ms - ``30`` - 33 ms - 50 ms - 20 * - ``25`` - 40 ms - ``20`` - 50 ms - 80 ms - 12.5 * - ``25`` - 40 ms - ``25`` - 40 ms - 40 ms - 25 * - ``30`` - 33 ms - ``20`` - 50 ms - 66 ms - 15.2 * - ``30`` - 33 ms - ``30`` - 33 ms - 33 ms - 30.3 * - ``40`` - 25 ms - ``30`` - 33 ms - 50 ms - 20 * - ``60`` - 16 ms - ``30`` - 33 ms - 48 ms - 20.8 This is why ``sv_fps 30`` and ``snaps 30`` form a useful high-rate pair, while raising ``sv_fps`` to 40 or 60 does not produce 40 or 60 snapshots per second: the client request is capped at 30 and the due time is rounded up to a whole server frame. It is also why a client using ``snaps 20`` can receive *fewer* than 20 snapshots/s on a 25 or 30 Hz server. The two integer intervals do not accumulate fractional remainder; the next due time starts from the frame on which the last packet was sent. ``sv_fps`` also controls calls to the game module's frame update, not just network output. Raising it increases authoritative simulation work, snapshot construction opportunities, and bandwidth demand. Leave it at the stock 20 unless the server is intentionally provisioned and tested for a different cadence. If choosing 30, tell clients to use ``snaps 30``. Values above 30 can change game-update cadence but cannot overcome the stock 30-snapshot request ceiling. Prediction and presentation latency ----------------------------------- ``cl_timeNudge`` ~~~~~~~~~~~~~~~~ The stock client clamps ``cl_timeNudge`` to ``-30..30`` and computes presentation time as: .. code-block:: text presentation time = local realtime + synchronized server-time delta - cl_timeNudge A positive value moves presentation farther behind the estimated server time, leaving more room to interpolate through jitter at the cost of added visual latency. A negative value advances presentation time, reducing that buffer and increasing the chance that the client must extrapolate or transition without a future snapshot. ``0`` is the appropriate default. Change it only after observing a repeatable interpolation/jitter tradeoff; it does not alter packet transmission, server simulation, or measured network round-trip time. Prediction switches ~~~~~~~~~~~~~~~~~~~ Keep ``cg_nopredict 0`` and server ``g_synchronousClients 0`` for ordinary Internet play. With either no-prediction/synchronous mode active, the cgame interpolates the player instead of replaying buffered user commands locally. The server's synchronous mode also defers real client movement processing to the game frame. These are behavior/debug choices, not remedies for a poor connection. Keep ``cl_nodelta 0``. Setting it causes move packets to tell the server not to use an acknowledged snapshot as the delta base, increasing snapshot data. It is useful for protocol diagnosis, not routine tuning. Keep ``cg_predictItems 1`` unless testing item-prediction behavior. It selects predicted versus ordinary pickup-event handling; it is not a packet- or snapshot-scheduling control. ``pmove_fixed`` and ``pmove_msec`` affect movement time quantization on both the server and predicting client. Their stock defaults are ``0`` and ``8``; ``pmove_msec`` is forced into ``8..33`` when movement runs. They are not packet- or snapshot-rate controls and should not be changed as generic network optimizations. Diagnosing before changing values --------------------------------- The stock client and server expose enough diagnostics to identify the limiting stage: ``cg_lagometer 1`` Displays snapshot ping and loss history. A received snapshot is green when it is not marked rate-delayed, yellow when the snapshot flag carries the server's rate-delayed bit, and a missing snapshot sample is red. Repeated yellow samples point to ``rate``/``sv_maxRate`` or packet size; red samples indicate missing snapshot sequence entries and should not be treated as a request simply to raise ``rate``. ``cl_showSend 1`` Prints the number of user commands selected for a packet and the transmitted packet size; a dot marks a client frame on which the packet-rate gate did not allow a send. It is the direct way to verify FPS/``cl_maxpackets`` quantization and whether duplication is approaching the 32-command cap. ``showpackets 1`` and ``showdrop 1`` Print netchan send/receive sequencing, fragmentation, dropped sequences, and fragment errors. Use them temporarily because the console volume is high. ``net_profile`` / ``net_showprofile`` / ``net_dumpprofile`` Enable and display or dump the stock per-channel byte, packet-size, and fragmentation statistics. The active profile side is selected from the current client/server runtime state and the cvar value. ``sv_showAverageBPS 1`` Produces a server-side rolling report of compressed bytes per client, aggregate bytes, peak packet size, and compression ratio. The report is emitted through the developer-print path, so console developer visibility must also permit it. Return diagnostics to ``0`` after measuring. They report behavior; none improves the connection. Decision guide -------------- .. list-table:: Symptom-driven changes :header-rows: 1 :widths: 28 34 38 * - Observation - Change to consider - Do not assume * - Yellow rate-delayed lagometer samples - Raise client ``rate`` or server ``sv_maxRate`` if measured bandwidth permits; alternatively lower ``snaps``. - Raising ``cl_maxpackets`` cannot increase server snapshot allowance. * - Red dropped-snapshot samples - Investigate loss, path congestion, and fragmentation; sufficient spare downstream capacity may justify lowering ``rate`` or ``snaps``. - A higher ``rate`` repairs loss. It can worsen queueing on a constrained path. * - Too many commands in each outgoing packet - Raise actual packet cadence, lower rendered FPS, or reduce ``cl_packetdup``. - The configured ``cl_maxpackets`` value equals observed packets/s. * - High client-to-server input queueing on a clean link - Select an aligned FPS/packet pair such as 125/62 or 100/100. - ``cl_maxpackets`` must equal ``sv_fps``. * - Smooth snapshots but delayed visual response - Confirm prediction is enabled and return positive ``cl_timeNudge`` toward zero. - ``rate`` changes local input prediction. * - Jitter/extrapolation with no packet loss - Test a small positive ``cl_timeNudge`` or a lower, steadier snapshot cadence. - A negative nudge is safer; it spends interpolation buffer. * - Server uplink saturation - Set a sustainable ``sv_maxRate`` and/or use 20 Hz snapshots; budget the cap across active clients. - Raising ``sv_fps`` is free when clients retain ``snaps 20``. Settings that do not optimize gameplay traffic ----------------------------------------------- Leave the following at their defaults unless administering their specific policy: * ``sv_minPing`` and ``sv_maxPing`` only reject non-LAN clients during connection based on challenge ping. They do not improve an accepted client's latency. * ``cl_timeout`` and ``sv_timeout`` decide when silent peers are considered timed out. They do not change retransmission or packet cadence. * ``sv_zombietime`` controls how long a disconnected client remains in zombie state. * ``sv_padPackets`` appends literal padding bytes to snapshots. Keep it at ``0`` outside deliberate transport tests. * ``sv_packet_info`` reports connectionless commands; it is not a sequenced gameplay performance control. Source path map --------------- The principal stock behaviors described here are implemented in: * ``src/client/engine/client/client_lifecycle.c`` — stock client cvar defaults; * ``src/client/engine/com_frame.c`` — client frame limiter; * ``src/client/engine/client/cgame_frame.c`` — user-command generation, packet gate, packet duplication window, time nudge, and prediction clock; * ``src/client/ui/state/ui_update.c`` — stock connection-rate presets; * ``src/server/engine/server_client_message.c`` — userinfo rate/snaps clamps and bundled user-command processing; * ``src/server/engine/server_frame.c`` — integer server-frame scheduling; * ``src/server/engine/server_snapshot_send.c`` — snapshot due checks, rate equation, rate-delay flag, and fragment pacing; * ``src/client/cgame/hud/lagometer.c`` — lagometer interpretation; * ``src/client/cgame/prediction/cg_predictplayerstate_internal.c`` and ``src/server/game/client_think.c`` — prediction and movement-time behavior; and * ``src/qcommon/netchan.c`` and ``src/qcommon/net_profile.c`` — sequencing, fragmentation, drop reporting, and profile statistics.