United Offensive network tuning

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 United Offensive network protocol wire-format specification and the complete United Offensive cvar reference registry.

Executive recommendations

There is no single best value for every connection. For a stable broadband client, this is a coherent balanced profile:

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:

Suggested client profiles

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:

Suggested server profiles

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

Relevant stock cvars

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:

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:

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.

Idealized stock packet-rate examples

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:

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:

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:

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:

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:

Maximum-accounted-packet rate delay

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:

snapshot interval = floor(1000 / snaps) ms

It also converts sv_fps to a server-frame quantum:

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:

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.

Snapshot/server-frame alignment examples

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:

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

Symptom-driven changes

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.