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

PartOwnsYou 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

PhaseWire frameWallet RPCMath call
connect - - -
initINITopenSession -
simple spinSPINsettleSimpleplay
complex openOPENopenComplexopen
complex step (x N)STEP -step
complex closeCLOSEcloseComplexclose
autoclose (external) -closeComplexautoclose | policy
resumeINIT (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)