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 dev, adapter-your-provider prod, or write your own) |
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 (× N) | STEP | — | step |
| complex close | CLOSE | closeComplex | close |
| autoclose (external) | — | closeComplex | autoclose | policy |
| resume | INIT (with resume payload) | openSession | — |
Money moves once per simple round and twice per complex round — 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)