Legacy Networking¶
Recorded so that nobody is tempted to preserve any of it. This entire layer is deleted.
Sources: libraries/sv_chatbox.lua, metatables/sv_player.lua (SetCSVar),
libraries/sv_player.lua (the auto-broadcast), sv_umsgs.lua, sh_init.lua:46.
1. The four transports¶
The original uses four different mechanisms, none of which exists in a modern engine.
| Transport | Direction | Used for |
|---|---|---|
usermessage / umsg.* |
Server → client | Everything discrete. 64 byte limit. |
datastream |
Server → client | Bulk tables too large for a usermessage |
NWVar / DTInt |
Server → all | Entity state (door flags, warrants, arrest, tied) |
concommand "cider" |
Client → server | Every client action, as an array of strings |
There is no use of the net library anywhere. Client→server traffic is entirely console
commands and datastream.Hook.
2. Usermessages¶
19 registered handlers. Names and the hot per-second variable names are string-pooled in
gamemode/sv_umsgs.lua — a manual optimisation that a modern engine does automatically.
| Message | Purpose |
|---|---|
cider_Inventory_Item |
One inventory delta: item:string, amount:long |
cider.chatBox.message / .playerMessage |
Chat lines |
CSVar |
The generic per-player variable channel (§3) |
MoneyAlert |
Money change notification |
cider_Menu, cider_BuyDoor, cider_CloseContainerMenu |
UI triggers |
cider_IncomingAccess, cider_massAccessSystem, cider_WipeAccess |
Access list editing |
cider.laws.update |
Law changes |
LogEvent |
Log lines to the client viewer |
TeamChange, PlayerKilled* |
Notifications |
ViewModFP / ViewModTP / ClearViewMod |
Camera modification |
The 64 byte cap applies to all of them, so any long string — a chat line, a door name, an item description — is silently truncated (D-06).
datastream covers what does not fit: helpReplace (the full help table, sent on
PlayerInitialized), cider_Laws, and container and vehicle payloads — 17 call sites.
3. CSVars — the bespoke replication system¶
meta:SetCSVar(class, key, value) (metatables/sv_player.lua:914) is a hand-rolled typed
variable channel:
CLASS_STRING · CLASS_LONG · CLASS_SHORT · CLASS_CHAR
CLASS_FLOAT · CLASS_BOOL · CLASS_VECTOR · CLASS_ANGLE · CLASS_ENTITY
Each write sends a CSVar usermessage carrying the class byte, the key string and the value,
received client-side as cider._LocalPlayerVariable.
The change check is broken¶
if (self.CSVars[var] == nil or self.CSVars ~= value) then
The second clause compares self.CSVars — the table itself — against value. A table is
never equal to a number or string, so the condition is always true. The dirty check never
fires and every SetCSVar call sends a message regardless of whether anything changed.
Auto-broadcast¶
player.AddAutoCSVar(type, name) registers a variable to be re-sent every second, from
the 1 s branch of the master tick (libraries/sv_player.lua). Thirteen are registered:
_NextSpawnGender _Gender _ScaleDamage _HideHealthEffects _Sleeping
_GPS _beTied _Stunned _StuckInWorld _JobTimeLimit
_JobTimeExpire _Salary (+ _Money, sent manually alongside)
Combined with the broken dirty check, this is 14 usermessages per player per second, forever, whether or not any value changed. Most of these are booleans that change a handful of times per session. A 32-player server sends ~450 messages/second to communicate almost nothing (D-11).
4. Entity state¶
NWBool / NWString for named state ("Arrested", "Tied", "Incapacitated",
"Exausted", "Warrant", "Name", "cider_ownerName", "cider_Container"), and
GetDTInt(3) as an untyped bitfield for door and container flags
(D-16).
Note that _Locked exists as a plain Lua field and as the OBJ_LOCKED bit, updated
separately — two sources of truth for one fact.
5. Client → server¶
Everything: concommand.Add("cider", …).
cider door buy
cider giveitem lexi cake 5
Raw strings, no types, no bounds. The dispatcher checks argument count and an access flag,
then pcalls. Rate limiting is per-command _Next* timestamps written by hand in each
handler. See 09_CHAT_AND_COMMANDS.md and
D-02.
6. Summary of failures¶
| Failure | Effect |
|---|---|
| Broken dirty check + timed rebroadcast | Constant bandwidth proportional to player count, independent of activity |
| Delta-only inventory sync with no reconciliation | One lost message desyncs a client until relog |
| 64-byte frame cap | Silent truncation of any long string |
| No client→server type validation | Every handler parses by hand; an entire class of exploit surface |
| Four transports, no abstraction | Every payload hand-serialised at the call site |
| Untyped bitfields and stringly-typed NWVars | No schema; meaning held by convention across files |
| Two sources of truth for lock state | They can disagree |
7. What the new design takes from this¶
Nothing structural. But the requirements the legacy layer was trying to meet are real, and they carry forward:
- Per-player private state (money, salary, hunger) →
[Sync]on a player-owned component, replicated to the owner. - Public entity state (door locked, owner name, container flag) →
[Sync]properties on the relevant component, visible to all. - Discrete events (a chat line, a notification, a money alert) →
[Rpc.Broadcast]or a targeted RPC, not a state variable. - Bulk data (help text, law text, container contents) → sent on demand, once, not polled.
- Client requests →
[Rpc.Host]with typed parameters, re-validated host-side.
The full specification is Architecture/07_NETWORKING.md.
The rule that falls out of this document: replicate state on change, send events as events, and never poll. Every legacy networking defect is a violation of one of those three.