Random
local Random = require("@game/ServerScriptService/Twill/Random")
local prize = Random.Pick(lootTable)local code = Random.Id(16)Server only, because an outcome a client can draw is an outcome it can choose.
Every draw comes from a cryptographic generator rather than from math.random,
so knowing every earlier result says nothing about the next one.
Rounds
Section titled “Rounds”A round makes an outcome auditable. Publish its commitment before anything is drawn, reveal the seed afterwards, and the result is provably older than the announcement.
local round = Random.Commit()announce(round.Commitment)
local prize = round:Pick(lootTable)
announce(round:Reveal())Anyone checks it with Random.Verify(commitment, seed) and then replays the
same draws with Random.Replay(seed).
A round holds 32 seed bytes. Its commitment is the SHA256 of those bytes, and its reveal is the same bytes as hexadecimal. Draws come from keyed Blake3 over the seed, 64 bytes of stream at a time, so any implementation of Blake3 reproduces them.
The stream is ordered. Replaying a different sequence of calls produces a different sequence of results, and the audit then fails for a reason that has nothing to do with honesty.
For the full flow, see Run a draw a player can audit.
Even draws only
Section titled “Even draws only”Pick and Shuffle treat every entry as equally likely. For a table where some
entries come up more often than others, use Chance,
which takes a round from here and draws from its stream, so a weighted outcome
is as auditable as an even one.
Random.Bytes
Section titled “Random.Bytes”[Server]
Returns the requested number of bytes that nobody can predict.
function Random.Bytes(count: number): bufferReturns
buffer - That many unpredictable bytes.
Throws when the count is not a whole number of zero or more.
Random.Int
Section titled “Random.Int”[Server]
Returns a whole number between the bounds, both of them reachable.
function Random.Int(min: number, max: number): numberReturns
number - A value between the bounds, every one equally likely.
Throws on fractional bounds, an inverted pair, or a span wider than 32 bits.
min == max is legal and returns that value.
Values that do not divide evenly into the generator’s word are drawn again rather than folded, because folding makes the low outcomes likelier than the high ones. That is the bias a loot table must not have, and it is why the span is bounded.
Random.Number
Section titled “Random.Number”[Server]
Returns a fraction between the bounds.
function Random.Number(min: number?, max: number?): numberParameters
| Name | Type | Description |
|---|---|---|
min | number? | Lowest value that may come back. Zero when left out. |
max | number? | Highest value that may come back. One when left out. |
Returns
number - A value between the bounds.
Throws when either bound is given and is not finite.
Random.Pick
Section titled “Random.Pick”[Server]
Returns one entry of a list, every entry equally likely.
function Random.Pick<T>(list: { T }): TReturns
T - One of them.
Throws when the list is empty.
Random.Shuffle
Section titled “Random.Shuffle”[Server]
Reorders a list, every ordering equally likely.
function Random.Shuffle<T>(list: { T }): { T }Returns
{ T } - That same list, reordered.
Throws when given something other than a table.
This reorders in place. The list handed in is the list handed back, so anything else holding it sees the new order too. Shuffling a shared loot table rearranges it for every later reader. Copy it first when the original order matters:
local order = Random.Shuffle(table.clone(LOOT))Random.Id
Section titled “Random.Id”[Server]
Returns text of the given length that nobody can guess ahead of time.
function Random.Id(length: number): stringReturns
string - Text of that length, drawn from an alphabet of 62 letters and digits.
Throws when the length is not a whole number above zero.
Length is the only thing that decides how hard it is to guess. Use it for redeem
codes, session ids, and the Token secret.
Twill.Token.Configure({ Secret = Random.Id(64) })Random.Commit
Section titled “Random.Commit”[Server]
Opens a round whose commitment can be published before anything is drawn.
function Random.Commit(): Round
export type Round = { Commitment: string, Int: (self: Round, min: number, max: number) -> number, NextNumber: (self: Round) -> number, Pick: <T>(self: Round, list: { T }) -> T, Reveal: (self: Round) -> string,}Returns
Round - A round, with its Commitment ready to publish.
Publish the commitment first, draw, then reveal. In that order it proves something; in any other order it proves nothing.
Random.Replay
Section titled “Random.Replay”[Server]
Reopens a revealed round so its draws can be taken again.
function Random.Replay(seed: string): RoundReturns
Round - A round that draws exactly what the original drew.
Throws when the seed is not hexadecimal, or not 32 bytes of it.
Random.Verify
Section titled “Random.Verify”[Server]
Reports whether a revealed seed is the one a commitment was made to.
function Random.Verify(commitment: string, seed: string): booleanReturns
boolean - true only when the seed matches the commitment.
Anything malformed answers false rather than raising, because this reads
untrusted input: the point is that a player can hand you a seed to check.
The verifyroll command puts this in the admin
console, so a moderator settles a dispute in front of the player without either
of them having to trust the other.
Round:Int
Section titled “Round:Int”[Server]
Returns a whole number between the bounds, taken from this round’s stream.
function round:Int(min: number, max: number): numberReturns
number - A value between the bounds, both reachable.
Throws on the same bounds Random.Int refuses.
Round:NextNumber
Section titled “Round:NextNumber”[Server]
Returns a fraction of one, taken from this round’s stream.
function round:NextNumber(): numberReturns
number - A value from zero, up to but never reaching one.
Named to match the method an ordinary Random carries, so anything drawing from
one draws from a round without knowing which it was handed.
Chance is what this exists for.
Two words of the stream are taken per call rather than one, giving 53 bits of fraction. A round drawing fractions therefore consumes its stream twice as fast as one drawing whole numbers, which matters only when replaying an interleaved sequence.
Round:Pick
Section titled “Round:Pick”[Server]
Returns one entry of a list, taken from this round’s stream.
function round:Pick<T>(list: { T }): TReturns
T - One of them, every entry equally likely.
Throws when the list is empty.
Round:Reveal
Section titled “Round:Reveal”[Server]
Hands back the seed so anyone can check the commitment and replay the draws.
function round:Reveal(): stringReturns
string - The seed as hexadecimal, safe to publish as text.
Reveal only after the round is over. Revealing it earlier tells everybody what is coming, because the stream is fixed by the seed.
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Seed | 32 bytes |
| Revealed seed as text | 64 hexadecimal characters |
Widest span Int draws | 2^32 values |
| Stream block | 64 bytes |
Id alphabet | 62 characters |
NextNumber precision | 53 bits |