Dedicated Server
Plume3D can run a game’s authoritative logic headless as a dedicated server — no
window, no Vulkan, no render loop — in a persistent fixed-tick loop. The same plume3d
binary is both the client (plume3d <app>) and the server (plume3d --server <app>);
your main.wren chooses server-vs-client behavior.
plume3d --server <appdir> # also --server=<appdir>The server:
- Loads
<appdir>/main.wrenand constructs yourGameclass. - Brings up the networking transport and listens on
[Network].port. - Calls
Game.init()once (networking is up, soNetworks ininit()). - Calls
Game.update(dt)every tick at[Network].tick_rateHz (default 60), servicing networking each tick. - Exits on
Engine.exit(code)orSIGINT/SIGTERM.
Keep the server code path renderer-free — the server has no window/renderer, so don’t call rendering, GUI, or audio APIs in code that runs server-side. Branch on server vs. client.
Because the engine listens automatically in --server mode, a dedicated server does not
call Net.startServer — it just drains Net.poll() each tick and responds with
Net.send. See the worked example apps/net_echo_server.
Configuration — [Network]
Section titled “Configuration — [Network]”Add a [Network] section to game.toml:
[Network]port = 7777 # UDP port the dedicated server listens ontick_rate = 60 # server Game.update(dt) rate, in Hzmax_clients = 32 # advisory cap on simultaneous connectionsport is what --server binds; tick_rate drives the loop. All keys are optional
(defaults 7777 / 60 / 32).
Shutdown & exit codes
Section titled “Shutdown & exit codes”Engine.exit(code)from Wren ends the loop with that code (clamped 0–255).SIGINT/SIGTERMstops the loop cleanly — how a host/orchestrator (including PlayFlow) stops a server.
| Code | Meaning |
|---|---|
value of Engine.exit(code) | Requested by the game. |
1 | Game.init() / Game.update() raised a Wren error, or main.wren failed to compile. |
2 | Setup error: app dir missing or no main.wren. |
70 | Filesystem initialization failed. |
Deterministic simulation
Section titled “Deterministic simulation”Use the seeded Random API so a fixed seed reproduces a server run —
important for authoritative simulation and for reproducing bugs.
Networking — the Net API
Section titled “Networking — the Net API”The transport is live: an authoritative dedicated-server model over
GameNetworkingSockets (reliable-ordered and unreliable UDP messages, encrypted), exposed
to Wren as Net. A dedicated server drains events each tick and responds:
import "engine" for Engine, Logger, Net
class Game { construct new() {} init() {} update(dt) { for (ev in Net.poll()) { if (ev["type"] == "connect") Logger.info("peer %(ev["conn"]) joined") if (ev["type"] == "data") Net.send(ev["conn"], ev["payload"], true) // echo if (ev["type"] == "disconnect") Logger.info("peer %(ev["conn"]) left") } } draw() {}}A client connects with Net.connect(host, port) and sends with Net.send(conn, bytes, reliable). The apps/net_echo_server + apps/net_echo_client pair is a complete
client/server example (run the server with --server, the client windowed). The
apps/net_loopback_demo is self-contained — it connects to itself, so run it via --server,
not as a client. See the full Net API.
Replication
Section titled “Replication”The engine can sync entity transforms server-authoritatively. The server calls
Net.setReplicatedEntity(id, x,y,z, qx,qy,qz,qw) each tick (the engine broadcasts a snapshot
automatically); the client reads Net.replicatedEntities() (or ...Lerp(alpha) for smooth
motion). See the replication section of the Net API
and apps/net_replication_demo.
PlayFlow cloud hosting
Section titled “PlayFlow cloud hosting”The PlayFlow API matchmakes a client to a PlayFlow-hosted dedicated server:
PlayFlow.requestServer(region, customData) → poll PlayFlow.serverInfo(id) until
status == "running" → Net.connect(host, port). Package the Linux server with
docker/server.Dockerfile; PlayFlow injects the port via PLUME_SERVER_PORT and stops the
server with SIGTERM. The client key comes from PLAYFLOW_CLIENT_KEY (never committed). The
client + packaging are built and mock-tested; a live PlayFlow account is needed to run it
end-to-end.
See the Net and PlayFlow API references, the
Testing (Headless) guide for --test, and Configuration for
the full game.toml reference.