Extensions
Small packages a math file imports. There is no plugin system, TS math
can just import, so an extension is an ordinary package.
Generator<S> = (next) => Grid<S> // makes a board
Selector<S> = (grid, next) => Pos[] // picks cells
Effect = (grid, pos, next) => Grid // changes a board
Three signatures carry all of it. next is always
host.rng_next, nothing here makes randomness, everything
receives it, and that is what lets one recorded seed replay a round.
New here? Build a slot walks the whole set end to end, from an empty grid to a running server.
The packages
- @open-rgs/grid: Shape is a height per column, not width by height.
- @open-rgs/weights: The number you read is the number you get.
- @open-rgs/strips: A fixed sequence, a stop, and the window it shows.
- @open-rgs/selectors: One primitive serving placement and hold-and-win targeting.
- @open-rgs/fill: Weighted draws, one cell at a time.
- @open-rgs/markov: Changes how clumpy reels look. Provably does not change frequency.
- @open-rgs/recipes: Every board comes from exactly one declared recipe.
- @open-rgs/paytable: One shared notion of payout, wild and scatter.
- @open-rgs/pay-lines: Longest run, paid once.
- @open-rgs/pay-ways: Paid by the product of per-column counts.
- @open-rgs/pay-anywhere: Total count, position irrelevant.
- @open-rgs/pay-cluster: A wild belongs to every cluster it touches.
- @open-rgs/cascade: Clear, fall, refill, repeat.
- @open-rgs/holdwin: A landing resets the counter.
- @open-rgs/freespins: A spin is paid at the multiplier that was showing.
- @open-rgs/picks: A pick round is priced by what it removes.
- @open-rgs/gamble: The interesting number is the edge, not the payout.
- @open-rgs/symbols: What a symbol turns into after the board is drawn.
- @open-rgs/meters: Collect five of these and something happens.
- @open-rgs/multiways: Each reel draws its own height, every spin.
- @open-rgs/big-symbols: One symbol across a block of cells.
- @open-rgs/scatters: The trigger rate is a parameter, not an emergent property.
Writing your own
Every extension point is one of the three signatures, so extending means writing a function. "Collect only from the same column" is a selector, not a new engine.
const sameColumn = (src) => (grid, next) =>
coinCells()(grid, next).filter((p) => p.col === src.col);
const columnCollector = (src) => collector(sameColumn(src), JACKPOTS); The one rule: take next, never make it. Code
that reaches for Math.random() looks right, passes every test
you would think to write, and silently voids any RTP measured from a replay.