Use this file to discover all available pages before exploring further.
Every byte that travels between a TF2 server and its clients flows through a single abstraction: the net channel (CNetChan). The channel multiplexes reliable and unreliable message queues over UDP, fragments large payloads into sub-channels, and exposes flow statistics to the engine. On top of it sits a typed message layer (INetMessage) that both server-to-client (SVC_*) and client-to-server (CLC_*) messages implement. String tables keep precache lists and other shared string data synchronized without consuming per-tick bandwidth.
Large messages (files, baselines) are split into fragments and tracked across up to eight parallel sub-channels:
// engine/net_chan.h (lines 30–79)#define NET_FRAMES_BACKUP 64 // must be power of 2#define NET_FRAMES_MASK (NET_FRAMES_BACKUP - 1)#define MAX_SUBCHANNELS 8 // 8 alternative send & wait bits#define SUBCHANNEL_FREE 0 // free to use#define SUBCHANNEL_TOSEND 1 // data pending, not sent yet#define SUBCHANNEL_WAITING 2 // sent, waiting for ACK#define SUBCHANNEL_DIRTY 3 // marked dirty during changelevelstruct subChannel_s{ int startFraggment[MAX_STREAMS]; int numFragments[MAX_STREAMS]; int sendSeqNr; int state; // one of SUBCHANNEL_* above int index;};
Each netframe_t records latency, dropped packets, and per-message-group byte counts that the net graph and net_stats commands display:
// engine/net_chan.h (lines 82–95)typedef struct netframe_s{ float time; // net_time received/sent int size; // total size in bytes float latency; // raw ping for this packet float avg_latency; // smoothed ping bool valid; // false if dropped, lost, flushed int choked; // count of previously choked packets int dropped; float m_flInterpolationAmount; unsigned short msggroups[INetChannelInfo::TOTAL]; // bytes by group} netframe_t;
CNetMessage carries a single m_bReliable flag. When set, the channel retransmits the message until it is acknowledged; when cleared, the message is sent once and silently dropped if the packet is lost:
Used for state that must arrive: signon, class info, string table creation, ConVar syncs, file transfers. If the underlying UDP packet is lost, the engine retransmits automatically.
Unreliable messages
Used for high-frequency, time-sensitive data: tick heartbeats, voice data, movement commands. Losing an occasional packet is acceptable; retransmitting stale data would be worse.
The message system uses three prefixes to indicate direction and purpose:
NET_* (bidirectional)
CLC_* (client → server)
SVC_* (server → client)
Messages that flow in either direction. Declared with DECLARE_NET_MESSAGE.
// common/netmessages.h// Synchronize ConVar valuesclass NET_SetConVar : public CNetMessage{ DECLARE_NET_MESSAGE( SetConVar ); // ... CUtlVector<cvar_t> m_ConVars;};// Execute a console command string on the remote peerclass NET_StringCmd : public CNetMessage{ DECLARE_NET_MESSAGE( StringCmd ); const char *m_szCommand;};// Tick heartbeat — always unreliableclass NET_Tick : public CNetMessage{ DECLARE_NET_MESSAGE( Tick ); // m_bReliable = false in constructor int m_nTick; float m_flHostFrameTime; float m_flHostFrameTimeStdDeviation;};// Signon state negotiationclass NET_SignonState : public CNetMessage{ DECLARE_NET_MESSAGE( SignonState ); int m_nSignonState; // SIGNONSTATE_* constant int m_nSpawnCount; // server session number};
Client-to-server messages. Declared with DECLARE_CLC_MESSAGE.
// common/netmessages.h// Packed user-command batch (mouse/keyboard input)class CLC_Move : public CNetMessage{ DECLARE_CLC_MESSAGE( Move ); // m_bReliable = false int m_nBackupCommands; // redundant cmd history int m_nNewCommands; int m_nLength; bf_read m_DataIn; bf_write m_DataOut;};// Voice audio payloadclass CLC_VoiceData : public CNetMessage{ DECLARE_CLC_MESSAGE( VoiceData ); int m_nLength; bf_read m_DataIn; bf_write m_DataOut; uint64 m_xuid;};// Acknowledge a received entity baselineclass CLC_BaselineAck : public CNetMessage{ DECLARE_CLC_MESSAGE( BaselineAck ); int m_nBaselineTick; int m_nBaselineNr; // 0 or 1};// Initial handshake — CRC of send tables, custom file hashesclass CLC_ClientInfo : public CNetMessage{ DECLARE_CLC_MESSAGE( ClientInfo ); CRC32_t m_nSendTableCRC; int m_nServerCount; bool m_bIsHLTV; uint32 m_nFriendsID; CRC32_t m_nCustomFiles[MAX_CUSTOM_FILES];};
Server-to-client messages. Declared with DECLARE_SVC_MESSAGE.
// common/netmessages.h// First message after connection: server metadataclass SVC_ServerInfo : public CNetMessage{ DECLARE_SVC_MESSAGE( ServerInfo ); int m_nProtocol; // must equal PROTOCOL_VERSION bool m_bIsDedicated; bool m_bIsHLTV; char m_cOS; // 'L' = Linux, 'W' = Win32 float m_fTickInterval; int m_nMaxClients; int m_nPlayerSlot; const char *m_szGameDir; // "tf2" const char *m_szMapName; const char *m_szHostName;};// Transmit a SendTable descriptor to the clientclass SVC_SendTable : public CNetMessage{ DECLARE_SVC_MESSAGE( SendTable ); bool m_bNeedsDecoder; int m_nLength; bf_read m_DataIn; bf_write m_DataOut;};// Map server class IDs to class names / data table namesclass SVC_ClassInfo : public CNetMessage{ DECLARE_SVC_MESSAGE( ClassInfo ); bool m_bCreateOnClient; // client creates from game.dll CUtlVector<class_t> m_Classes; int m_nNumServerClasses;};
To add a new server-to-client message, you declare the class with DECLARE_SVC_MESSAGE(MyMsg), implement ReadFromBuffer / WriteToBuffer, and add a ProcessMyMsg virtual to IServerMessageHandler.
Entity state is transmitted as delta packets: only properties that changed since the last acknowledged tick are included. The flow is:
1
Baseline snapshot
On initial connection the server sends a full entity snapshot (the “baseline”) via SVC_PacketEntities with m_bIsDelta = false. The client stores this as baseline 0 or 1 (alternating, identified by m_nBaseline).
2
Delta generation
Each tick, the server compares current property values against the last acknowledged snapshot per client. Only changed bits are packed into the delta using the SendProp bit widths declared in each SendTable.
3
Client acknowledgment
The client sends CLC_BaselineAck to confirm it received and applied the baseline, so the server knows which snapshot to delta from next time.
4
Apply and interpolate
The client’s RecvVarProxyFn functions write incoming values into C_BaseEntity fields. The interpolation system then smooths between the last two received snapshots to produce per-frame positions.
If the client’s CLC_ClientInfo.m_nSendTableCRC does not match the server’s send table CRC, the connection is rejected before any entity data is sent. Keep send and recv table definitions in sync.
String tables provide a lightweight mechanism to synchronize named string lists — model names, sound precaches, downloadable file lists, map name — without sending the strings in every packet.
// engine/networkstringtable.h (lines 41–98)class CNetworkStringTable : public INetworkStringTable{public: CNetworkStringTable( TABLEID id, const char *tableName, int maxentries, int userdatafixedsize, int userdatanetworkbits, bool bIsFilenames ); const char *GetTableName( void ) const; TABLEID GetTableId( void ) const; int GetNumStrings( void ) const; int GetMaxStrings( void ) const; // Networking void SetTick( int tick ); bool ChangedSinceTick( int tick ) const; int AddString( bool bIsServer, const char *value, int length = -1, const void *userdata = NULL ); const char *GetString( int stringNumber ); // Per-client delta write/parse (server side) int WriteUpdate( CBaseClient *client, bf_write &buf, int tick_ack ); void ParseUpdate( bf_read &buf, int entries ); // HLTV rollback support void EnableRollback(); void RestoreTick( int tick ); bool WriteBaselines( SVC_CreateStringTable &msg, char *msg_buffer, int msg_buffer_size );};
Each table entry has an optional userdata blob. For modelprecache, this blob carries the model’s MDLCacheFlattenedBone_t to pre-warm the model cache on the client.
ChangedSinceTick(tick) lets the server answer “has this table changed since the client’s last acknowledged tick?” efficiently. The engine calls this before including a string table update in a packet to avoid sending unchanged tables:
// engine/networkstringtable.h (lines 58–59)void SetTick( int tick );bool ChangedSinceTick( int tick ) const;
SourceTV is integrated at the network channel level. An HLTV proxy appears to the server as a normal client (its player_info_t.ishltv flag is true) but fans out the stream to thousands of spectators.
The CNetworkStringTable::EnableRollback() / RestoreTick(tick) API exists specifically for HLTV: when a spectator seeks backward in a live demo, the string table state can be rolled back to any previously recorded tick without a full reconnect.
Demo files embed the raw net channel stream, including all SVC_* messages and string table baselines. The backward-compatibility constants in proto_version.h (PROTOCOL_VERSION_17 through PROTOCOL_VERSION_23) tell the demo reader exactly which field layout to expect for each recorded protocol version.
The server advertises itself to Valve’s master server over UDP using the A2S_* query protocol. Clients use SVC_ServerInfo.m_szHostName, m_nMaxClients, and m_szMapName to populate the in-game server browser. None of this logic lives in the game DLL — it is entirely engine-owned and requires no source changes to work.
Every arrow over the UDP wire is managed by CNetChan. Reliable messages ride in the same packet stream but are retransmitted in subsequent packets until acknowledged by the receiver’s sequence number.