How it works
One Bun process. One port. Four parts. Each part is one interface
in @open-rgs/contract - swap any of them without
touching the others.
The picture
flowchart TD
Client([CLIENT])
subgraph RGS["one Bun process . one port"]
direction TB
Transport["TRANSPORT"]
Orch["ORCHESTRATOR<br/>. Lua math via wasmoon"]
Transport --> Orch
end
Admin["admin http<br/>/livez . /healthz<br/>/admin/manifest<br/>/admin/autoclose"]
Adapter["PLATFORM ADAPTER"]
Operator([OPERATOR])
Client -->|"binary-msgpack ws<br/>frame = type + msgpack"| Transport
Admin -. http .-> Orch
Orch -->|"PlatformAdapter<br/>openSession . settleSimple<br/>openComplex . closeComplex"| Adapter
Adapter -->|"operator wire - ws . rest . grpc . ...<br/>one WS, or three services"| Operator The four parts
| Part | Owns | You usually |
|---|---|---|
| Transport | WS upgrade, frame decode, admin HTTP on the same port | use the bundled binaryTransport |
| Orchestrator | round lifecycle, mode resolution, bet computation, promo free-rounds, max-win cap, autoclose, metrics | never touch - that's the framework |
| Math | RTP, ops, awaiting hints. Currency-blind. RNG injected via host | write one Lua file per game shape |
| Adapter | session lifecycle, money movement, promo free-rounds, event stream | pick one (platform-mock for dev, or write your own wallet adapter for prod) |
A round, end to end
| Phase | Wire frame | Wallet RPC | Math call |
|---|---|---|---|
| connect | - | - | - |
| init | INIT | openSession | - |
| simple spin | SPIN | settleSimple | play |
| complex open | OPEN | openComplex | open |
| complex step (x N) | STEP | - | step |
| complex close | CLOSE | closeComplex | close |
| autoclose (external) | - | closeComplex | autoclose | policy |
| resume | INIT (with resume payload) | openSession | - |
A simple round is one settle RPC (carrying the debit and the credit together); a complex round is two RPCs (open = debit, close = credit) - never more. STEP is pure in-process: the math computes the next visual + state, no wallet is touched. Cash-out on a Mines / Chicken-Road style round is a client-initiated CLOSE, not a special STEP.
State boundary
RGS owns nothing durable. The wallet adapter is the source of
truth for balance, sessions, and cross-round carry. Math state
lives in-memory and rebuilds on reconnect from the adapter's
stored carry blob. Sessions evaporate on restart and
reappear on the next INIT.
What's on disk
hello-spin/
+-- package.json # @open-rgs/core + @open-rgs/contract + your adapter
+-- src/index.ts # one file - boots createServer
+-- maths/spin.lua # one file - your math
+-- Dockerfile # bun, one port, HEALTHCHECK on /readyz
+-- .env # adapter creds (never committed)