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

FieldPurpose
multiplierdimensionless win multiplier; 0 = loss. Core computes win = multiplier × bet
opsopaque visual instructions; the client replays them. Core forwards verbatim
typegame-defined tag (e.g. "win", "loss", "trigger_fs")
carryopaque string threaded into the next round's prev on this session
next_moderoutes 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.

HelperBehaviour
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.