Reflex architecture
A Reflex module adds a server entry point to a regular client main.lua. Both sides belong to one module but run independently and receive different APIs.
ReflexModule/├── module.json # IsReflex: true└── scripts/ ├── main.lua # client └── server.lua # player's server contextResponsibilities
Section titled “Responsibilities”main.lua
Section titled “main.lua”- reads
InputActions; - builds ImGui and UXML;
- uses local cameras, audio, and graphics;
- stores local data in
Storage; - sends user intent and settings to the server.
server.lua
Section titled “server.lua”- runs work through
Scheduler:OnTick(); - reads authoritative game objects;
- invokes Reflex-server-only methods;
- validates client messages;
- sends only display data required by the client.
The Network boundary
Section titled “The Network boundary”The two sides do not share Lua tables or globals. Network is their direct channel.
main.lua ── SendTable ──▶ server.luamain.lua ◀─ SendTable ── server.luaMessages are serialized, so only strings, numbers, booleans, and nested tables cross the boundary. API objects do not.
Do not route decisions through the client
Section titled “Do not route decisions through the client”When a decision can be derived from state available to the server, keep the complete loop in server.lua:
server state → server decision → server action └→ client presentationThe server state → client decision → server action path adds a network round trip. The original state may already be stale when the command returns. This matters for movement, simulated button state, server-side look direction, aim targets, firing, and other tick-sensitive behavior.
InputActions are client-only, so a custom binding may send a mode activation, deactivation, or setting change to the server. After that, keep observation, timing, and supported mutations through AgentInput and Aim on the server. Do not stream commands every frame when one mode-state message is sufficient.
Send only the result required for UI, cameras, audio, or other local feedback back to the client. The server must not wait for client confirmation when it can make the decision itself.
Lifecycle
Section titled “Lifecycle”- The user enables and selects a Reflex module.
- Its client side starts in a game session.
- The server starts
server.luafor that player. - Both sides register callbacks and exchange messages.
- Hot reload replaces the previous instance.
Do not depend on the order of the first messages. Register OnTableReceived() before SendTable() and resend initial state after a short delay when necessary.
One Reflex module
Section titled “One Reflex module”Only one Reflex module can be active at a time. This rule is independent from regular functional tags.
Context matters
Section titled “Context matters”| API | Client | Reflex server |
|---|---|---|
InputActions, Storage, Localization |
✅ | ❌ |
ImGui, UI, Cameras, Textures |
✅ | ❌ |
Scheduler:OnFrame() |
✅ | ❌ |
Scheduler:OnTick() |
❌ | ✅ |
Network, Time, Agents |
✅ | ✅ |
Individual objects may expose different properties too. Always check context labels in the API reference.
Core principle
Section titled “Core principle”The client reports what the user intends; the server decides whether it is allowed and what the valid result is. Do not move a server decision to the client merely for UI convenience.
Continue with the Network protocol and server loop.