Client and Reflex
A regular module contains one client runtime. A Reflex module adds a separate server runtime and a transport between them:
client input/UI ── intent ──▶ Reflex decisionclient display ◀─ state ─── Reflex resultThe runtimes do not share variables, live objects or callbacks. A helper from
src/shared/ may be imported by both entries, but it is bundled and executed as
a separate copy in each runtime. Only serialized messages cross the live
client/Reflex boundary.
Decide ownership by effect
Section titled “Decide ownership by effect”| Work | Owner | Why |
|---|---|---|
| HUD, UXML, cameras, sound, hints | Client | These APIs exist only where the game renders them |
| Module controls and local key state | Client | Input actions are client-only |
| Visual lines and overlays | Client | They are presentation, not server entities |
| Decisions the server API can observe and apply | Reflex | They do not need to trust client-reported state |
| Server timing and repeated checks | Reflex | They should not wait for client frames or round trips |
| Showing a confirmed server result | Client | Reflex sends data; client chooses how to present it |
Ask: which runtime can observe the required state, apply the effect and verify the result? Put the decision there. Reflex runs on the server, but its module API is intentionally limited and visibility-filtered; server location alone does not make every possible decision authoritative. A client can display predicted or pending state, but it should not become the source of truth for state Reflex can enforce.
Project shape
Section titled “Project shape”A client-only module needs:
src/client/main.tsA Reflex module adds:
src/server/main.tssrc/shared/messages.tsClient files import @killscript/sdk/client; server files import
@killscript/sdk/server. The compiler rejects a client implementation import
from server code and the reverse. Plain interfaces and message types belong in
src/shared/.
Intent → decision → confirmed state
Section titled “Intent → decision → confirmed state”// Client: report what the player requested.controls.toggle.onPressed(() => { coreNetwork.send("setEnabled", { enabled: !displayedEnabled });});
coreNetwork.on("stateChanged", ({ enabled }) => { displayedEnabled = enabled; updateHud(enabled);});// Reflex: own and confirm the state.let enabled = false;
coreNetwork.on("setEnabled", ({ enabled: requested }) => { const local = Agents.GetLocalAgent(); enabled = local?.Stats.IsAlive === true && requested;
coreNetwork.send("stateChanged", { enabled, changedAtTick: Time.Tick, });});The client never claims that the request succeeded. It updates from the state Reflex accepted. Conversely, Reflex should not ask the client for information already available from server globals.
See Typed networking for the complete protocol declaration and same-tick batching behavior.
Scheduling in each runtime
Section titled “Scheduling in each runtime”// Client: presentation follows frames.import { scheduler } from "@killscript/sdk/client";scheduler.frame(updateCamera);// Reflex: simulation follows server ticks.import { scheduler } from "@killscript/sdk/server";scheduler.tick(updateServerState);There is no client OnTick or Reflex OnFrame in the exposed API. Use
Time.Tick inside a client frame callback when presentation needs to notice a
new simulation tick.