Update on my own thread from March. A few things changed since then, so here’s where asobi’s internals actually stand now.
The core idea is that game logic is authored in Lua, run server-side and server-authoritative, on top of Luerl. Each match is its own gen_statem process (asobi_match_server), one per match, supervised. The matchmaker is a plain gen_server that owns the queue and ticks it on an interval. Nothing exotic, just OTP doing what it does.
The Lua runs sandboxed. We take luerl:init and then strip the dangerous globals to nil before any script-author code touches the state - os.execute, os.exit, io, load, loadfile, dofile, the whole package library. require is replaced with our own that resolves relative to the script’s base dir, no parent traversal, no absolute paths. And every call into script-author code runs in a spawned process that gets killed on timeout, so a runaway loop in someone’s Lua can’t wedge the match process.
The part I like most is the reload path. Each match keeps its Luerl state next to the path of the script that produced it. On every tick we stat the file, and if the mtime moved we re-execute the script body against the current Luerl state, redeclaring globals and functions in place. So the in-flight game state (players, counters, tables) survives, connected players stay connected, and the running match just applies the new rules on the next tick. No server restart. If the new script has a syntax error we log it, remember the mtime so we don’t keep retrying, and keep running the old code until the file is fixed. The Erlang side reloads the normal BEAM way.
What shipped since March: 7 client SDKs now (Dart, Defold, Godot, JS, LÖVE, Unity, Unreal), and a zero-install demo where you play an actual match in the browser at asobi.dev/docs/samples if you want to see the loop before reading any code.
There’s also a CLI now (asobi-cli) that got a local-dev loop this month: asobi init mygame --template arena && cd mygame && asobi dev scaffolds a genre-shaped match.lua and runs the whole backend on localhost via Docker, no account anywhere. Paired with device-based guest auth (a game declares it as a Lua global, no signup/login flow), you can go from nothing to two local instances playing an actual match without ever touching the network side. Separately, since open registration is the default, I added a registration mode (open/oauth_only/closed) and a pluggable pre-auth client gate this month too, mostly to close a pbkdf2-cost DoS angle on the open path - mentioning it here because I’d guess this crowd has opinions on that kind of gate design.
One correction to the first post while I’m here. I wrote clustering back then. The honest current design is single-node, on purpose - the whole per-match, per-world model leans on local process state and I’d rather have that be simple and correct than distributed and hand-wavy. No built-in voice and no media relay either, that’s out of scope by design. It’s self-host first, there’s an “if asobi disappears” runbook so you’re never stuck, and there’s an invite-only managed option for people who don’t want to run it themselves.