Skip to main content

VetraNet

Full-stack network middleware for Vetra. Handles fire-request serialization, server-side authority (rate limiting, origin validation, behavior verification), authoritative bullet replication, and client-side cosmetic management, all over a single RemoteEvent.

VetraNet is accessed via Vetra.VetraNet. It is environment-aware: calling it on the server returns a handle with authority signals; calling it on the client returns a handle with :Fire().

Architecture Overview

Client                              Server
──────                              ──────
Net:Fire(context, "Rifle")
  → FireChannel.SendFire()          ← FireChannel decode
  → cosmetic spawned locally (+ latency buffer)
                                    ← FireValidator: origin, speed, behavior hash
                                    ← RateLimiter: token deduct
                                    ← Solver:Fire() → bullet lives on server
                                    ← OutboundBatcher.WriteFireForAll()
  ← cosmetic echoed (all clients)
                                    ← hit events → OutboundBatcher.WriteHitForAll()
  ← state batches (every Heartbeat, if ReplicateState = true)
  ← DriftCorrector lerps cosmetics toward server position

Setup

Shared registration (ModuleScript required by both sides):

-- SharedBehaviors.lua (in ReplicatedStorage)
local Vetra = require(ReplicatedStorage.Vetra)

local Registry = Vetra.VetraNet.BehaviorRegistry.new()
Registry:Register("Rifle",   RifleBehavior)
Registry:Register("Shotgun", ShotgunBehavior)
return Registry
Registration order

Both server and client must register behaviors in the same order with the same names. Fire payloads carry only a 2-byte u16 hash, if the hash tables diverge, every fire request will be rejected as RejectedUnknownBehavior. Enforce this by requiring the same shared registration module on both sides.

Server:

local SharedRegistry = require(ReplicatedStorage.SharedBehaviors)

local Net = Vetra.VetraNet.new(ServerSolver, SharedRegistry, {
    MaxOriginTolerance     = 20,
    TokensPerSecond        = 10,
    BurstLimit             = 20,
    ReplicateState         = true,
})

Net.OnValidatedHit:Connect(function(owner, context, result, velocity, impactForce)
    -- apply damage, update leaderboard, etc.
end)

Net.OnFireRejected:Connect(function(player, reason)
    warn(player.Name .. " fire rejected: " .. reason)
end)

Client:

local SharedRegistry = require(ReplicatedStorage.SharedBehaviors)

local Net = Vetra.VetraNet.new(ClientSolver, SharedRegistry)

-- Fire a bullet over the network
local Context = BulletContext.new({ Origin = muzzlePosition, Direction = direction, Speed = speed })
Net:Fire(Context, "Rifle")

NetworkConfig

All fields are optional. Unset fields fall back to built-in defaults. See TypeDefinitions.NetworkConfig for the complete interface.

Field Type Default Description
MaxOriginTolerance number 15 Max studs between client-reported and server-reconstructed fire origin.
MaxConcurrentPerPlayer number 20 Maximum bullets a player may have in flight simultaneously.
TokensPerSecond number 10 Token-bucket refill rate for fire-rate limiting.
BurstLimit number 20 Maximum burst tokens. Must be >= TokensPerSecond.
DriftThreshold number 2 Studs of drift before the client corrector begins interpolating.
CorrectionRate number 8 Lerp speed for drift correction (studs per second).
LatencyBuffer number 0 Extra seconds to delay local cosmetic spawn. 0 = use measured RTT.
ReplicateState boolean true Broadcast bullet state every Heartbeat to all clients.
Mode NetworkMode "ClientAuthoritative" Authority mode, controls which side may call :Fire(). See Enums.NetworkMode.

NetworkMode

VetraNet supports three authority modes, set via NetworkConfig.Mode. Use Vetra.Enums.NetworkMode values rather than raw strings.

ClientAuthoritative (default)

Clients send fire requests. The server validates each request (rate limit, origin tolerance, behavior hash) and replicates approved bullets to all clients. Use this for standard player-fired weapons.

local Net = Vetra.VetraNet.new(ServerSolver, SharedRegistry, {
    Mode = Vetra.Enums.NetworkMode.ClientAuthoritative, -- or omit; this is the default
})

ServerAuthority

Only server code may initiate bullets by calling Net:Fire(). Any fire request that arrives from a client is silently dropped, clients cannot spawn network bullets at all. Use this for NPC projectiles, environmental hazards, or any weapon whose origin should be entirely server-controlled.

-- Server
local Net = Vetra.VetraNet.new(ServerSolver, SharedRegistry, {
    Mode = Vetra.Enums.NetworkMode.ServerAuthority,
})

-- Fire a bullet from server code; replicates to all clients automatically.
local Context = BulletContext.new({ Origin = origin, Direction = direction, Speed = speed })
Net:Fire(Context, behaviorHash)

SharedAuthority

Both client and server may fire. Client requests go through the full validation pipeline as in ClientAuthoritative. Server calls bypass validation and replicate directly. Use this when player weapons and server-owned projectiles share the same handle and behavior registry — for example, a weapon that can also be triggered by a server script.

local Net = Vetra.VetraNet.new(ServerSolver, SharedRegistry, {
    Mode = Vetra.Enums.NetworkMode.SharedAuthority,
})

Properties

BehaviorRegistry

VetraNet.BehaviorRegistry: BehaviorRegistry

Re-export of the BehaviorRegistry module. Access via Vetra.VetraNet.BehaviorRegistry.

OnValidatedHit

VetraNet.OnValidatedHit: Signal

(Server only)

Fired after a hit report passes all server-side authority checks: rate-limit, concurrent-bullet limit, origin tolerance, behavior validity, and trajectory reconstruction. Safe to use for damage application.

Signal signature: (owner: Player, context: BulletContext, result: RaycastResult?, velocity: Vector3, impactForce: number)

  • result is nil for speed/distance expiry events (no surface was struck).
  • impactForce is computed as BulletMass x velocity.Magnitude. Returns 0 when BulletMass is not set on the behavior.

OnFireRejected

VetraNet.OnFireRejected: Signal

(Server only)

Fired when a fire request is rejected before a bullet is spawned. Useful for anti-cheat logging, telemetry, and kick thresholds.

Signal signature: (player: Player, reason: string)

Rejection reasons:

Reason Description
"RejectedNoSession" Player has no active VetraNet session (not yet registered).
"RejectedRateLimit" Player exceeded their token-bucket fire rate.
"RejectedConcurrentLimit" Player already has MaxConcurrentPerPlayer bullets in flight.
"RejectedUnknownBehavior" Behavior hash not found in the server's registry.
"RejectedOriginTolerance" Fire origin too far from the server-reconstructed position.
"RejectedInvalidSpeed" Reported speed exceeds the behavior's MaxSpeed.

Functions

new

VetraNet.new(
SolverVetra,--

The live Vetra solver instance.

BehaviorRegistryBehaviorRegistry,--

Shared pre-populated registry.

NetworkConfigNetworkConfig?,--

Optional configuration overrides.

OnCosmeticFireSignal?,--

(Client only) Fired after a cosmetic bullet spawns locally.

OnCosmeticHitSignal?--

(Client only) Fired after a cosmetic bullet terminates on a confirmed hit.

) → VetraNetServer | VetraNetClient

Creates a new VetraNet handle. Returns a server or client handle depending on the environment (RunService:IsServer()).

On the server returns a handle with .OnValidatedHit, .OnFireRejected, and :Destroy().

On the client returns a handle with :Fire() and :Destroy().

The optional OnCosmeticFire and OnCosmeticHit signals are client-only. Pass pre-created VeSignal instances if you need to hook into cosmetic events outside the standard signal table.

-- Server
local Net = Vetra.VetraNet.new(ServerSolver, SharedRegistry, {
    TokensPerSecond = 15,
    BurstLimit      = 30,
    ReplicateState  = true,
})

-- Client
local Net = Vetra.VetraNet.new(ClientSolver, SharedRegistry)

Fire

This item only works when running on the server. Server
VetraNet:Fire(
ContextBulletContext,--

Caller-created context carrying origin, direction, speed, UserData, and RaycastParams.

BehaviorHashnumber--

Numeric hash from BehaviorRegistry:GetHash(name).

) → number--

Server cast ID, or 0 on failure.

(Server only, ServerAuthority and SharedAuthority modes)

Fires a server-owned bullet and replicates it to all clients. Bypasses all validation (rate limit, origin tolerance, behavior hash checks) because the server is considered trusted.

Only available when Mode is ServerAuthority or SharedAuthority. Calling this in ClientAuthoritative mode logs an error and returns 0.

The caller creates a BulletContext with the desired fire parameters. UserData and RaycastParams set on the context are forwarded to the solver automatically, no separate call needed.

local Net = Vetra.VetraNet.new(ServerSolver, SharedRegistry, {
    Mode = Vetra.Enums.NetworkMode.ServerAuthority,
})

-- Fire from an NPC or scripted event:
local origin    = npc.HumanoidRootPart.Position + Vector3.new(0, 1, 0)
local direction = (targetPosition - origin).Unit
local Context   = BulletContext.new({ Origin = origin, Direction = direction, Speed = 600 })
Context.UserData = { Npc = npc }

Net:Fire(Context, SharedRegistry:GetHash("Rifle"))

Returns the server-assigned cast ID on success, or 0 on failure (unknown behavior hash, wrong mode).

Fire

This item only works when running on the client. Client
VetraNet:Fire(
ContextBulletContext,--

Caller-created context carrying origin, direction, speed, and RaycastParams.

BehaviorNamestring--

Registered behavior name (must match server registry).

) → ()

(Client only)

Serializes and sends a fire request to the server, then spawns a local cosmetic bullet after the configured latency buffer.

The caller creates a BulletContext with the desired fire parameters. RaycastParams set on the context is used for the local cosmetic solver.

local Context = BulletContext.new({
    Origin    = tool.Handle.Position,
    Direction = direction.Unit,
    Speed     = 250,
})
Net:Fire(Context, "Rifle")

The cosmetic bullet is spawned locally with a small delay (latency buffer) to reduce visible RTT jitter. DriftCorrector lerps it toward the server-confirmed position once state replication arrives.

Silently no-ops and logs a warning if the behavior name is not registered in the client registry.

SetPlayerFilter

This item only works when running on the server. Server
VetraNet:SetPlayerFilter(
Fn((playerPlayer) → boolean)?--

Predicate, or nil to disable.

) → ()

(Server only)

Sets a predicate that gates which players receive replicated fire, hit, and state messages. The function is called once per candidate player on every broadcast; return true to include them, false to exclude.

Pass nil to clear the filter and revert to all-player broadcast (default).

The shooter's own fire echo is never filtered, the shooter always receives confirmation of their own cast regardless of the predicate.

-- Replicate only to players on the Blue team
Net:SetPlayerFilter(function(player)
    return player.Team == Teams.Blue
end)

-- Clear the filter, back to full broadcast
Net:SetPlayerFilter(nil)

Destroy

VetraNet:Destroy() → ()

Tears down this VetraNet handle. Disconnects all RemoteEvent listeners, stops the Heartbeat frame loop, and destroys all internal state including signals.

CAUTION

Call this only when shutting down the game or a specific weapon system entirely. Do not call on every player disconnect, VetraNet is designed to live for the duration of the server/client lifetime.

Show raw api
{
    "functions": [
        {
            "name": "new",
            "desc": "Creates a new VetraNet handle. Returns a server or client handle\ndepending on the environment (`RunService:IsServer()`).\n\nOn the **server** returns a handle with `.OnValidatedHit`, `.OnFireRejected`,\nand `:Destroy()`.\n\nOn the **client** returns a handle with `:Fire()` and `:Destroy()`.\n\nThe optional `OnCosmeticFire` and `OnCosmeticHit` signals are client-only.\nPass pre-created `VeSignal` instances if you need to hook into cosmetic\nevents outside the standard signal table.\n\n```lua\n-- Server\nlocal Net = Vetra.VetraNet.new(ServerSolver, SharedRegistry, {\n    TokensPerSecond = 15,\n    BurstLimit      = 30,\n    ReplicateState  = true,\n})\n\n-- Client\nlocal Net = Vetra.VetraNet.new(ClientSolver, SharedRegistry)\n```",
            "params": [
                {
                    "name": "Solver",
                    "desc": "The live Vetra solver instance.",
                    "lua_type": "Vetra"
                },
                {
                    "name": "BehaviorRegistry",
                    "desc": "Shared pre-populated registry.",
                    "lua_type": "BehaviorRegistry"
                },
                {
                    "name": "NetworkConfig",
                    "desc": "Optional configuration overrides.",
                    "lua_type": "NetworkConfig?"
                },
                {
                    "name": "OnCosmeticFire",
                    "desc": "(Client only) Fired after a cosmetic bullet spawns locally.",
                    "lua_type": "Signal?"
                },
                {
                    "name": "OnCosmeticHit",
                    "desc": "(Client only) Fired after a cosmetic bullet terminates on a confirmed hit.",
                    "lua_type": "Signal?"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "VetraNetServer | VetraNetClient"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 193,
                "path": "docs/VetraNet.lua"
            }
        },
        {
            "name": "Fire",
            "desc": "*(Server only, `ServerAuthority` and `SharedAuthority` modes)*\n\nFires a server-owned bullet and replicates it to all clients.\nBypasses all validation (rate limit, origin tolerance, behavior hash checks)\nbecause the server is considered trusted.\n\nOnly available when `Mode` is `ServerAuthority` or `SharedAuthority`.\nCalling this in `ClientAuthoritative` mode logs an error and returns `0`.\n\nThe caller creates a `BulletContext` with the desired fire parameters.\n`UserData` and `RaycastParams` set on the context are forwarded to the\nsolver automatically, no separate call needed.\n\n```lua\nlocal Net = Vetra.VetraNet.new(ServerSolver, SharedRegistry, {\n    Mode = Vetra.Enums.NetworkMode.ServerAuthority,\n})\n\n-- Fire from an NPC or scripted event:\nlocal origin    = npc.HumanoidRootPart.Position + Vector3.new(0, 1, 0)\nlocal direction = (targetPosition - origin).Unit\nlocal Context   = BulletContext.new({ Origin = origin, Direction = direction, Speed = 600 })\nContext.UserData = { Npc = npc }\n\nNet:Fire(Context, SharedRegistry:GetHash(\"Rifle\"))\n```\n\nReturns the server-assigned cast ID on success, or `0` on failure\n(unknown behavior hash, wrong mode).",
            "params": [
                {
                    "name": "Context",
                    "desc": "Caller-created context carrying origin, direction, speed, UserData, and RaycastParams.",
                    "lua_type": "BulletContext"
                },
                {
                    "name": "BehaviorHash",
                    "desc": "Numeric hash from `BehaviorRegistry:GetHash(name)`.",
                    "lua_type": "number"
                }
            ],
            "returns": [
                {
                    "desc": "Server cast ID, or `0` on failure.",
                    "lua_type": "number"
                }
            ],
            "function_type": "method",
            "realm": [
                "Server"
            ],
            "source": {
                "line": 282,
                "path": "docs/VetraNet.lua"
            }
        },
        {
            "name": "Fire",
            "desc": "*(Client only)*\n\nSerializes and sends a fire request to the server, then spawns a\nlocal cosmetic bullet after the configured latency buffer.\n\nThe caller creates a `BulletContext` with the desired fire parameters.\n`RaycastParams` set on the context is used for the local cosmetic solver.\n\n```lua\nlocal Context = BulletContext.new({\n    Origin    = tool.Handle.Position,\n    Direction = direction.Unit,\n    Speed     = 250,\n})\nNet:Fire(Context, \"Rifle\")\n```\n\nThe cosmetic bullet is spawned locally with a small delay (latency buffer)\nto reduce visible RTT jitter. `DriftCorrector` lerps it toward the\nserver-confirmed position once state replication arrives.\n\nSilently no-ops and logs a warning if the behavior name is not registered\nin the client registry.",
            "params": [
                {
                    "name": "Context",
                    "desc": "Caller-created context carrying origin, direction, speed, and RaycastParams.",
                    "lua_type": "BulletContext"
                },
                {
                    "name": "BehaviorName",
                    "desc": "Registered behavior name (must match server registry).",
                    "lua_type": "string"
                }
            ],
            "returns": [],
            "function_type": "method",
            "realm": [
                "Client"
            ],
            "source": {
                "line": 315,
                "path": "docs/VetraNet.lua"
            }
        },
        {
            "name": "SetPlayerFilter",
            "desc": "*(Server only)*\n\nSets a predicate that gates which players receive replicated fire, hit,\nand state messages. The function is called once per candidate player on\nevery broadcast; return `true` to include them, `false` to exclude.\n\nPass `nil` to clear the filter and revert to all-player broadcast (default).\n\nThe shooter's own fire echo is **never filtered**, the shooter always\nreceives confirmation of their own cast regardless of the predicate.\n\n```lua\n-- Replicate only to players on the Blue team\nNet:SetPlayerFilter(function(player)\n    return player.Team == Teams.Blue\nend)\n\n-- Clear the filter, back to full broadcast\nNet:SetPlayerFilter(nil)\n```",
            "params": [
                {
                    "name": "Fn",
                    "desc": "Predicate, or `nil` to disable.",
                    "lua_type": "((player: Player) -> boolean)?"
                }
            ],
            "returns": [],
            "function_type": "method",
            "realm": [
                "Server"
            ],
            "source": {
                "line": 342,
                "path": "docs/VetraNet.lua"
            }
        },
        {
            "name": "Destroy",
            "desc": "Tears down this VetraNet handle. Disconnects all `RemoteEvent`\nlisteners, stops the Heartbeat frame loop, and destroys all\ninternal state including signals.\n\n:::caution\nCall this only when shutting down the game or a specific weapon system\nentirely. Do not call on every player disconnect, VetraNet is designed\nto live for the duration of the server/client lifetime.\n:::",
            "params": [],
            "returns": [],
            "function_type": "method",
            "source": {
                "line": 357,
                "path": "docs/VetraNet.lua"
            }
        }
    ],
    "properties": [
        {
            "name": "BehaviorRegistry",
            "desc": "Re-export of the [BehaviorRegistry] module. Access via\n`Vetra.VetraNet.BehaviorRegistry`.",
            "lua_type": "BehaviorRegistry",
            "source": {
                "line": 160,
                "path": "docs/VetraNet.lua"
            }
        },
        {
            "name": "OnValidatedHit",
            "desc": "*(Server only)*\n\nFired after a hit report passes all server-side authority checks:\nrate-limit, concurrent-bullet limit, origin tolerance, behavior validity,\nand trajectory reconstruction. Safe to use for damage application.\n\nSignal signature:\n`(owner: Player, context: BulletContext, result: RaycastResult?, velocity: Vector3, impactForce: number)`\n\n- `result` is `nil` for speed/distance expiry events (no surface was struck).\n- `impactForce` is computed as `BulletMass x velocity.Magnitude`. Returns `0`\n  when `BulletMass` is not set on the behavior.",
            "lua_type": "Signal",
            "source": {
                "line": 220,
                "path": "docs/VetraNet.lua"
            }
        },
        {
            "name": "OnFireRejected",
            "desc": "*(Server only)*\n\nFired when a fire request is rejected before a bullet is spawned.\nUseful for anti-cheat logging, telemetry, and kick thresholds.\n\nSignal signature: `(player: Player, reason: string)`\n\n**Rejection reasons:**\n\n| Reason | Description |\n|--------|-------------|\n| `\"RejectedNoSession\"` | Player has no active VetraNet session (not yet registered). |\n| `\"RejectedRateLimit\"` | Player exceeded their token-bucket fire rate. |\n| `\"RejectedConcurrentLimit\"` | Player already has `MaxConcurrentPerPlayer` bullets in flight. |\n| `\"RejectedUnknownBehavior\"` | Behavior hash not found in the server's registry. |\n| `\"RejectedOriginTolerance\"` | Fire origin too far from the server-reconstructed position. |\n| `\"RejectedInvalidSpeed\"` | Reported speed exceeds the behavior's `MaxSpeed`. |",
            "lua_type": "Signal",
            "source": {
                "line": 243,
                "path": "docs/VetraNet.lua"
            }
        }
    ],
    "types": [],
    "name": "VetraNet",
    "desc": "Full-stack network middleware for Vetra. Handles fire-request\nserialization, server-side authority (rate limiting, origin validation,\nbehavior verification), authoritative bullet replication, and client-side\ncosmetic management, all over a **single `RemoteEvent`**.\n\nVetraNet is accessed via `Vetra.VetraNet`. It is environment-aware:\ncalling it on the server returns a handle with authority signals;\ncalling it on the client returns a handle with `:Fire()`.\n\n## Architecture Overview\n\n```\nClient                              Server\n──────                              ──────\nNet:Fire(context, \"Rifle\")\n  → FireChannel.SendFire()          ← FireChannel decode\n  → cosmetic spawned locally (+ latency buffer)\n                                    ← FireValidator: origin, speed, behavior hash\n                                    ← RateLimiter: token deduct\n                                    ← Solver:Fire() → bullet lives on server\n                                    ← OutboundBatcher.WriteFireForAll()\n  ← cosmetic echoed (all clients)\n                                    ← hit events → OutboundBatcher.WriteHitForAll()\n  ← state batches (every Heartbeat, if ReplicateState = true)\n  ← DriftCorrector lerps cosmetics toward server position\n```\n\n## Setup\n\n**Shared registration (ModuleScript required by both sides):**\n\n```lua\n-- SharedBehaviors.lua (in ReplicatedStorage)\nlocal Vetra = require(ReplicatedStorage.Vetra)\n\nlocal Registry = Vetra.VetraNet.BehaviorRegistry.new()\nRegistry:Register(\"Rifle\",   RifleBehavior)\nRegistry:Register(\"Shotgun\", ShotgunBehavior)\nreturn Registry\n```\n\n:::danger Registration order\nBoth server and client **must** register behaviors in the same order with\nthe same names. Fire payloads carry only a 2-byte u16 hash, if the hash\ntables diverge, every fire request will be rejected as `RejectedUnknownBehavior`.\nEnforce this by requiring the same shared registration module on both sides.\n:::\n\n**Server:**\n\n```lua\nlocal SharedRegistry = require(ReplicatedStorage.SharedBehaviors)\n\nlocal Net = Vetra.VetraNet.new(ServerSolver, SharedRegistry, {\n    MaxOriginTolerance     = 20,\n    TokensPerSecond        = 10,\n    BurstLimit             = 20,\n    ReplicateState         = true,\n})\n\nNet.OnValidatedHit:Connect(function(owner, context, result, velocity, impactForce)\n    -- apply damage, update leaderboard, etc.\nend)\n\nNet.OnFireRejected:Connect(function(player, reason)\n    warn(player.Name .. \" fire rejected: \" .. reason)\nend)\n```\n\n**Client:**\n\n```lua\nlocal SharedRegistry = require(ReplicatedStorage.SharedBehaviors)\n\nlocal Net = Vetra.VetraNet.new(ClientSolver, SharedRegistry)\n\n-- Fire a bullet over the network\nlocal Context = BulletContext.new({ Origin = muzzlePosition, Direction = direction, Speed = speed })\nNet:Fire(Context, \"Rifle\")\n```\n\n## NetworkConfig\n\nAll fields are optional. Unset fields fall back to built-in defaults.\nSee [TypeDefinitions.NetworkConfig] for the complete interface.\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `MaxOriginTolerance` | `number` | `15` | Max studs between client-reported and server-reconstructed fire origin. |\n| `MaxConcurrentPerPlayer` | `number` | `20` | Maximum bullets a player may have in flight simultaneously. |\n| `TokensPerSecond` | `number` | `10` | Token-bucket refill rate for fire-rate limiting. |\n| `BurstLimit` | `number` | `20` | Maximum burst tokens. Must be `>= TokensPerSecond`. |\n| `DriftThreshold` | `number` | `2` | Studs of drift before the client corrector begins interpolating. |\n| `CorrectionRate` | `number` | `8` | Lerp speed for drift correction (studs per second). |\n| `LatencyBuffer` | `number` | `0` | Extra seconds to delay local cosmetic spawn. `0` = use measured RTT. |\n| `ReplicateState` | `boolean` | `true` | Broadcast bullet state every Heartbeat to all clients. |\n| `Mode` | `NetworkMode` | `\"ClientAuthoritative\"` | Authority mode, controls which side may call `:Fire()`. See [Enums.NetworkMode]. |\n\n## NetworkMode\n\nVetraNet supports three authority modes, set via `NetworkConfig.Mode`.\nUse `Vetra.Enums.NetworkMode` values rather than raw strings.\n\n**`ClientAuthoritative`** *(default)*\n\nClients send fire requests. The server validates each request (rate limit,\norigin tolerance, behavior hash) and replicates approved bullets to all\nclients. Use this for standard player-fired weapons.\n\n```lua\nlocal Net = Vetra.VetraNet.new(ServerSolver, SharedRegistry, {\n    Mode = Vetra.Enums.NetworkMode.ClientAuthoritative, -- or omit; this is the default\n})\n```\n\n**`ServerAuthority`**\n\nOnly server code may initiate bullets by calling `Net:Fire()`. Any fire\nrequest that arrives from a client is silently dropped, clients cannot\nspawn network bullets at all. Use this for NPC projectiles, environmental\nhazards, or any weapon whose origin should be entirely server-controlled.\n\n```lua\n-- Server\nlocal Net = Vetra.VetraNet.new(ServerSolver, SharedRegistry, {\n    Mode = Vetra.Enums.NetworkMode.ServerAuthority,\n})\n\n-- Fire a bullet from server code; replicates to all clients automatically.\nlocal Context = BulletContext.new({ Origin = origin, Direction = direction, Speed = speed })\nNet:Fire(Context, behaviorHash)\n```\n\n**`SharedAuthority`**\n\nBoth client and server may fire. Client requests go through the full\nvalidation pipeline as in `ClientAuthoritative`. Server calls bypass\nvalidation and replicate directly. Use this when player weapons and\nserver-owned projectiles share the same handle and behavior registry —\nfor example, a weapon that can also be triggered by a server script.\n\n```lua\nlocal Net = Vetra.VetraNet.new(ServerSolver, SharedRegistry, {\n    Mode = Vetra.Enums.NetworkMode.SharedAuthority,\n})\n```",
    "source": {
        "line": 151,
        "path": "docs/VetraNet.lua"
    }
}