Math · simple
A simple round resolves in one math call. Use it for slots, instant-win, dice, plinko, and any game where the outcome is determined by a single roll.
Module shape
-- maths/spin.lua
return {
kind = "simple",
name = "hello",
version = "0.1.0",
rtp = 0.95,
play = function(prev, ctx)
local r = host.rng_next()
local m = (r < 0.30 and 0.5) or (r < 0.40 and 2) or (r < 0.41 and 50) or 0
return {
multiplier = m,
ops = { { kind = "result", multiplier = m } },
type = m > 0 and "win" or "loss",
-- carry? = "<opaque string>", -- threaded into next play()'s prev
-- next_mode? = "freespins", -- routes the next round
}
end,
} Return shape
| Field | Purpose |
|---|---|
multiplier | dimensionless win multiplier; 0 = loss. Core computes win = multiplier × bet |
ops | opaque visual instructions; the client replays them. Core forwards verbatim |
type | game-defined tag (e.g. "win", "loss", "trigger_fs") |
carry | opaque string threaded into the next round's prev on this session |
next_mode | routes the next round into a specific mode id |
Context (ctx)
ctx = {
mode = "default", -- resolved mode id (after promo / next_mode override)
cheat? = { force_win = true, force_coeff = 5 }, -- dev-only, stripped in prod
params? = { mines = 3 }, -- free-form game params from the SPIN request
} Host helpers
Two helpers are injected into every math VM. Extensions registered
via loadLuaMath(path, { extensions: […] }) add more.
| Helper | Behaviour |
|---|---|
host.rng_next() | returns a float in [0, 1) from the orchestrator's RNG |
host.log_debug(msg) | writes a debug line tagged with the math file path |
host.mark.count(name) | increments a named counter — inert in production, recorded by the simulator |
host.mark.observe(name, v) | appends a value to a named histogram |
host.mark.tag(name) | tags the current spin under name |
host.mark.contribute(name, m) | adds a multiplier to a named RTP-attribution bucket |
Currency-blindness
Math never sees balance, bet, or currency. The orchestrator
multiplies the returned multiplier by the resolved bet
(allowedBets[betIndex] × priceMultiplier × stakeMultiplier)
and applies the manifest's max-win cap before settling.
Loading
import { loadLuaMath } from "@open-rgs/core";
import reels from "@open-rgs/ext-reels";
const math = await loadLuaMath("./maths/spin.lua", {
extensions: [reels], // optional native + Lua helpers, see open-rgs-ext-*
marks: false, // true to wire the simulator's mark collector
});
The loader computes the math's SHA-256 and stamps it on every
round (visible in /healthz). Math version mismatch
against stored session carry triggers the
manifest.recovery policy.