Skip to content

Network Protocols

Vanguard's network protocol is the client/server compatibility contract for remote hierarchy, discovery, call shapes, Player identity, guard timing, error transport, and service proxy construction.

Package version and protocol version are intentionally separate. A package can add local utilities without changing protocol. A package must change protocol when old clients and new servers can no longer safely interpret the same replicated remote layout.

Current Protocol

Vanguard 0.1.15 uses network protocol 2.

Release Protocol Compatibility note
0.1.15 2 Adds remote-kind metadata and the Replicator remote kind
0.1.14 1 Last protocol 1 release
0.1.13 1 Protocol-compatible with 0.1.14

Mixed protocol installs fail during client service proxy construction with VG-NET-001. Install the same package on both runtimes.

Handshake Attributes

The server publishes these attributes on _VanguardRemotes:

Attribute Protocol 2 value Purpose
VanguardProtocol 2 Hard compatibility gate for remote layout and semantics
VanguardVersion Server package version Diagnostic signal for mixed package installations

The client requires an exact protocol match before it builds a service proxy. Package versions may differ only when the protocol matches; Vanguard warns because local behavior, fixes, or utilities may still differ.

Client protocol Server protocol Result
2 2 Proxy construction continues
2 Missing VG-NET-001; proxy construction stops
2 1 VG-NET-001; upgrade the older runtime
1 2 VG-NET-001; upgrade the older runtime

Protocol 2 Remote Hierarchy

Protocol 2 publishes remotes beneath the Vanguard package ModuleScript:

Vanguard [ModuleScript]
└── _VanguardRemotes [Folder]
    ├── VanguardProtocol = 2 [Attribute]
    ├── VanguardVersion = "0.1.15" [Attribute]
    └── <ServiceName> [Folder]
        ├── <FunctionName> [RemoteFunction]
        ├── <SignalName> [RemoteEvent or UnreliableRemoteEvent]
        ├── <PropertyName> [Folder]
        │   ├── VanguardRemoteKind = "Property" [Attribute]
        │   ├── _Get [RemoteFunction]
        │   └── _Changed [RemoteEvent]
        └── <ReplicatorName> [Folder]
            ├── VanguardRemoteKind = "Replicator" [Attribute]
            ├── _Get [RemoteFunction]
            └── _Changed [RemoteEvent]

Only services with at least one supported Client member receive a service folder. A purely server-side service is intentionally absent from the client protocol surface.

Service and remote names come directly from registration names and Client table keys. Renaming one is a game API change even when Vanguard's protocol number stays the same.

Remote Kind Metadata

Protocol 2 adds VanguardRemoteKind to folder-backed remotes:

Kind Folder shape Client proxy
Property _Get, _Changed RemoteProperty.Client
Replicator _Get, _Changed Replicator.Client

Protocol 1 inferred any folder with _Get and _Changed as a property. Protocol 2 needs explicit metadata because properties and replicators share a similar folder shape but have different change payloads.

Publication Timeline

On the server, startup publishes remotes in this order:

  1. module definitions are registered;
  2. plugin BeforeStart hooks run;
  3. plugin init hooks run;
  4. network options and service rules validate;
  5. service folders, guards, functions, signals, properties, and replicators are compiled;
  6. service VanguardInit priority groups complete;
  7. plugin start hooks run;
  8. service VanguardStart hooks are scheduled;
  9. _VanguardRemotes is parented beneath the Vanguard package;
  10. components start and readiness completes.

The server builds the remote tree before service init, but keeps the root folder unparented while required initialization runs. A failed init therefore cannot expose a partially-ready remote API to clients.

Client Discovery Handshake

The first client GetService(name) performs this sequence:

  1. return the cached proxy when one already exists;
  2. wait for _VanguardRemotes for at most RemoteTimeout seconds;
  3. read VanguardProtocol and VanguardVersion;
  4. reject a protocol other than 2 with VG-NET-001;
  5. warn when the server package version differs;
  6. wait for the named service folder for at most RemoteTimeout seconds;
  7. map each recognized child to a client proxy member;
  8. cache the completed service proxy.

RemoteTimeout defaults to 15 seconds. A timeout uses VG-NET-002. Increasing it can accommodate intentionally long server init, but it does not repair a failed server bootstrap or a service with no client remotes.

Remote Function Contract

Generated client methods support dot and colon calls:

InventoryService.GetItems()
InventoryService:GetItems()

For colon calls, the generated proxy removes its own service table before invoking the RemoteFunction. Only explicit application arguments cross the Roblox boundary.

Roblox supplies the sending Player; clients cannot choose that argument. Vanguard invokes service client methods as:

clientMethod(callContext, player, ...payload)

The temporary callContext provides:

  • self.Server: the owning server service;
  • self.<RemoteName>: another entry from the service's Client table;
  • writes to normal keys: updates the Client table;
  • writes to self.Server: rejected as read-only.

Application errors thrown by the service method propagate through RemoteFunction. With ServicePromises = true, the generated client method catches that transport error and rejects a Vanguard Promise.

Signal Contract

CreateSignal() maps to RemoteEvent. CreateUnreliableSignal() attempts to use UnreliableRemoteEvent and falls back to RemoteEvent when unavailable.

Client-to-server signal payloads pass through the inbound guard once before any listener runs. Server listeners receive:

function(player, ...payload)

A rejected event is dropped; one-way signal transport has no client rejection response.

Server-to-client signal delivery supports Fire, FireFor, FireAll, FireExcept, and FireWhere. Client listeners receive only the application payload.

Remote Property Contract

A property is a folder marked VanguardRemoteKind = "Property" with _Get and _Changed.

ClientProperty:Get() invokes _Get. The request passes through rate limiting, validation, authentication, and verification with remote type Property.

Property update operations:

  • Set(value) updates the global value and fires _Changed to all clients;
  • SetFor(player, value) stores an override and fires only to that Player;
  • ClearFor(player) removes an override and sends the current global value.

Property values are internally packed so nil remains a valid stored value.

Replicator Contract

A replicator is a folder marked VanguardRemoteKind = "Replicator" with _Get and _Changed.

Client reads invoke _Get and pass through the guard pipeline with remote type Replicator. The server returns the Player override when one exists, otherwise the global state.

Server update operations:

  • Set(value) replaces the global state;
  • SetPath(path, value) updates one path in a table state;
  • Patch(patch) recursively merges a patch table;
  • SetFor, SetPathFor, and PatchFor apply only to one Player;
  • ClearFor(player) removes a Player override and sends the global state.

Replicator change events carry:

operation, path, value

where operation is Set, SetPath, or Patch. Clients apply the change to their hydrated local copy and fire local observers with the resulting state and change descriptor.

Guard Pipeline On The Wire

Every inbound function, client-fired signal, property read, and replicator read uses one compiled guard. Protocol 2 enforces this order:

  1. Rate limit consumes one unit for the Roblox-supplied Player.
  2. Validate checks client payload only.
  3. Global authenticate checks server-wide identity/session policy.
  4. Remote authenticate checks merged service and named-remote policy.
  5. Global verify checks server-wide contextual policy.
  6. Remote verify authorizes the specific action and payload.

Malformed requests consume rate-limit capacity because rate limiting happens first. Every callback must explicitly return true. Returning false, nil, or throwing rejects the request.

The callback context contains:

type NetworkContext = {
    Player: Player,
    Service: any,
    ServiceName: string,
    RemoteName: string,
    RemoteType: "Function" | "Signal" | "Property" | "Replicator",
    Arguments: { any } & { n: number },
}

Arguments.n preserves trailing nil payload values.

Rejection And Error Transport

Protocol 2 keeps the stable rejection names introduced in protocol 1:

Rejection Catalog code Client-visible behavior
RATE_LIMITED VG-NET-101 Function/property/replicator error; signal dropped
INVALID_PAYLOAD VG-NET-102 Function/property/replicator error; signal dropped
UNAUTHENTICATED VG-NET-103 Function/property/replicator error; signal dropped
UNVERIFIED VG-NET-104 Function/property/replicator error; signal dropped
GUARD_ERROR VG-NET-105 Generic function/property/replicator error; signal dropped

Function, property, and replicator read errors retain the rejection name and include a direct error-catalog URL. Internal guard exceptions remain server-only in rejection detail, logs, and OnRejected.

Legacy Protocol 1 Deep Reference

Vanguard 0.1.13 and 0.1.14 use protocol 1.

Protocol 1 publishes this hierarchy:

Vanguard [ModuleScript]
└── _VanguardRemotes [Folder]
    ├── VanguardProtocol = 1 [Attribute]
    ├── VanguardVersion = "0.1.14" [Attribute]
    └── <ServiceName> [Folder]
        ├── <FunctionName> [RemoteFunction]
        ├── <SignalName> [RemoteEvent or UnreliableRemoteEvent]
        └── <PropertyName> [Folder]
            ├── _Get [RemoteFunction]
            └── _Changed [RemoteEvent]

Protocol 1 defines:

  • _VanguardRemotes as the only replicated remote root;
  • service folders named after registered server services;
  • RemoteFunction children for client-callable functions;
  • RemoteEvent or UnreliableRemoteEvent children for signals;
  • property folders identified by _Get and _Changed children;
  • Roblox-supplied Player identity as the first server payload argument;
  • the six-stage guard order;
  • rejection names used for metrics and client errors;
  • client discovery through WaitForChild with RemoteTimeout;
  • version warnings when package versions differ but protocols match.

Protocol 1 does not include:

  • VanguardRemoteKind;
  • replicator remotes;
  • a way to distinguish one folder-backed remote kind from another.

That limitation is why 0.1.15 moves to protocol 2.

Serialization And Delivery Boundaries

Vanguard relies on Roblox remote serialization. It does not add a custom serializer, schema negotiation, compression, retry layer, ordering layer, or transaction system.

Consequences:

  • only Roblox-serializable payloads can cross the boundary;
  • validators run after Roblox has delivered a decoded payload to the server;
  • unreliable delivery guarantees come from UnreliableRemoteEvent;
  • a successful guard does not lock game state before mutation;
  • critical mutation code should re-check state that can change concurrently;
  • server-to-client data is visible to the receiving client and is not secret.

What Requires A Protocol Bump

A future Vanguard release should change the protocol number when clients and servers can no longer safely interpret the same replicated contract. Examples:

  • renaming _VanguardRemotes, _Get, or _Changed;
  • changing service, property, or replicator folder structure;
  • changing function argument placement or Player identity semantics;
  • changing marker-to-remote mappings;
  • requiring new handshake attributes;
  • changing rejection transport in a way old clients cannot interpret.

These changes do not require a protocol bump by themselves:

  • local-only utilities;
  • class registry or member-access features;
  • plugin hooks that do not alter replicated remotes;
  • richer logs, error codes, and documentation links;
  • type-export improvements;
  • bug fixes that preserve hierarchy and call contracts.

Diagnostics

print(Vanguard.Version)
print(Vanguard.NetworkProtocol)

local info = Vanguard.GetNetworkInfo()
print(info.Protocol, info.ServerVersion)

On the server, GetNetworkInfo() is available immediately. On the client, fields populate during first remote-container discovery.

When Studio reports a protocol mismatch:

  1. inspect the Wally _Index version in both server and client stacks;
  2. confirm both bootstraps require the same package alias;
  3. run wally install in the game project;
  4. remove stale or duplicate package copies;
  5. restart Rojo sync and the play session.

Continue with Networking for API usage, Replicators for state trees, Network Security for policy, and Errors for diagnostics.