Skip to main content

BehaviorRegistry

Pre-registered behavior hash table shared between server and client.

The core insight is that full behavior tables are never sent over the network. Instead, both server and client register the same behaviors at startup with identical names and registration order. Fire payloads carry only a 2-byte u16 hash. The server resolves the full behavior by hash lookup, zero serialization cost, zero deserialization cost, and zero ability for the client to inject or modify a behavior by crafting a custom table.

-- SharedBehaviors.lua (required by both server and client)
local Registry = Vetra.VetraNet.BehaviorRegistry.new()
Registry:Register("Rifle",    RifleBehavior)
Registry:Register("Shotgun",  ShotgunBehavior)
Registry:Register("Grenade",  GrenadeBehavior)
return Registry
Registration order

Both server and client must register behaviors in the same order with the same names. If they diverge, hashes will not match and every fire request will be rejected as RejectedUnknownBehavior. Enforce this by requiring the same shared ModuleScript on both sides, never register behaviors conditionally or in a different order per environment.

Set MaxSpeed on every behavior

Always set MaxSpeed on every registered behavior via BehaviorBuilder:Physics():MaxSpeed(n):Done(). The registry logs a warning if MaxSpeed is missing, without it, FireValidator falls back to a global default cap rather than your per-weapon limit, which weakens server-side speed validation.

Functions

new

BehaviorRegistry.new() → BehaviorRegistry

Creates a new empty registry.

Register

BehaviorRegistry:Register(
Namestring,--

Non-empty behavior name.

BehaviorVetraBehavior--

The built behavior table (from BehaviorBuilder:Build()).

) → number--

Assigned u16 hash, or 0 if registration failed (e.g. empty name).

Registers a named behavior and returns its assigned u16 hash.

Registering the same name twice is idempotent, the existing hash is returned without creating a duplicate entry. Registering the same behavior table under a different name produces a separate hash (intentional, weapon variants may share physics but carry different cosmetic behaviors).

local RifleHash   = Registry:Register("Rifle",   RifleBehavior)
local ShotgunHash = Registry:Register("Shotgun", ShotgunBehavior)
-- RifleHash == 1, ShotgunHash == 2 (first registered = hash 1, etc.)

Get

BehaviorRegistry:Get(Hashnumber) → VetraBehavior?

Returns the full behavior table for a given u16 hash.

Returns nil if the hash was never registered. The server treats an unrecognised hash as RejectedUnknownBehavior and rejects the fire request.

GetHash

BehaviorRegistry:GetHash(Namestring) → number--

Registered hash, or 0 if not found.

Returns the u16 hash for a given behavior name.

Returns 0 (UNKNOWN_BEHAVIOR_HASH) if the name has not been registered. Called internally by Client:Fire() to fill the BehaviorHash field in fire payloads before sending.

Destroy

BehaviorRegistry:Destroy() → ()

Destroys this registry, clearing all name and hash mappings.

Idempotent, safe to call more than once.

Show raw api
{
    "functions": [
        {
            "name": "new",
            "desc": "Creates a new empty registry.",
            "params": [],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "BehaviorRegistry"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 46,
                "path": "docs/BehaviorRegistry.lua"
            }
        },
        {
            "name": "Register",
            "desc": "Registers a named behavior and returns its assigned u16 hash.\n\nRegistering the same name twice is **idempotent**, the existing hash is\nreturned without creating a duplicate entry. Registering the same behavior\ntable under a different name produces a separate hash (intentional, weapon\nvariants may share physics but carry different cosmetic behaviors).\n\n```lua\nlocal RifleHash   = Registry:Register(\"Rifle\",   RifleBehavior)\nlocal ShotgunHash = Registry:Register(\"Shotgun\", ShotgunBehavior)\n-- RifleHash == 1, ShotgunHash == 2 (first registered = hash 1, etc.)\n```",
            "params": [
                {
                    "name": "Name",
                    "desc": "Non-empty behavior name.",
                    "lua_type": "string"
                },
                {
                    "name": "Behavior",
                    "desc": "The built behavior table (from `BehaviorBuilder:Build()`).",
                    "lua_type": "VetraBehavior"
                }
            ],
            "returns": [
                {
                    "desc": "Assigned u16 hash, or `0` if registration failed (e.g. empty name).",
                    "lua_type": "number"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 66,
                "path": "docs/BehaviorRegistry.lua"
            }
        },
        {
            "name": "Get",
            "desc": "Returns the full behavior table for a given u16 hash.\n\nReturns `nil` if the hash was never registered. The server treats an\nunrecognised hash as `RejectedUnknownBehavior` and rejects the fire request.",
            "params": [
                {
                    "name": "Hash",
                    "desc": "",
                    "lua_type": "number"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "VetraBehavior?"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 77,
                "path": "docs/BehaviorRegistry.lua"
            }
        },
        {
            "name": "GetHash",
            "desc": "Returns the u16 hash for a given behavior name.\n\nReturns `0` (UNKNOWN_BEHAVIOR_HASH) if the name has not been registered.\nCalled internally by `Client:Fire()` to fill the `BehaviorHash` field in\nfire payloads before sending.",
            "params": [
                {
                    "name": "Name",
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "returns": [
                {
                    "desc": "Registered hash, or `0` if not found.",
                    "lua_type": "number"
                }
            ],
            "function_type": "method",
            "source": {
                "line": 89,
                "path": "docs/BehaviorRegistry.lua"
            }
        },
        {
            "name": "Destroy",
            "desc": "Destroys this registry, clearing all name and hash mappings.\n\nIdempotent, safe to call more than once.",
            "params": [],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 96,
                "path": "docs/BehaviorRegistry.lua"
            }
        }
    ],
    "properties": [],
    "types": [],
    "name": "BehaviorRegistry",
    "desc": "Pre-registered behavior hash table shared between server and client.\n\nThe core insight is that full behavior tables are **never sent over the\nnetwork**. Instead, both server and client register the same behaviors at\nstartup with identical names and registration order. Fire payloads carry\nonly a 2-byte u16 hash. The server resolves the full behavior by hash\nlookup, zero serialization cost, zero deserialization cost, and zero\nability for the client to inject or modify a behavior by crafting a\ncustom table.\n\n```lua\n-- SharedBehaviors.lua (required by both server and client)\nlocal Registry = Vetra.VetraNet.BehaviorRegistry.new()\nRegistry:Register(\"Rifle\",    RifleBehavior)\nRegistry:Register(\"Shotgun\",  ShotgunBehavior)\nRegistry:Register(\"Grenade\",  GrenadeBehavior)\nreturn Registry\n```\n\n:::danger Registration order\nBoth server and client **must** register behaviors in the **same order**\nwith the **same names**. If they diverge, hashes will not match and every\nfire request will be rejected as `RejectedUnknownBehavior`. Enforce this\nby requiring the same shared ModuleScript on both sides, never register\nbehaviors conditionally or in a different order per environment.\n:::\n\n:::tip Set MaxSpeed on every behavior\nAlways set `MaxSpeed` on every registered behavior via\n`BehaviorBuilder:Physics():MaxSpeed(n):Done()`. The registry logs a warning\nif `MaxSpeed` is missing, without it, `FireValidator` falls back to a\nglobal default cap rather than your per-weapon limit, which weakens\nserver-side speed validation.\n:::",
    "source": {
        "line": 39,
        "path": "docs/BehaviorRegistry.lua"
    }
}