On this page
tecs.ecs.random
Seeded generation, in named streams a snapshot carries.
tecs.ecs.random.seed(world, 20260726)
local loot <const> = tecs.ecs.random.stream(world, "game.loot")
local spawns <const> = tecs.ecs.random.stream(world, "game.spawns")
local roll <const> = loot:integer(1, 20)Streams
The world seed and stream name determine each Random sequence. Drawing from loot never advances spawns, so adding a stream leaves existing consumers unchanged. Use namespaced, nonempty names and call seed during world setup. Calling it later restarts every stream.
Use newRandom for tools, tests, and benchmarks that need a generator outside a world. Do not share a generator between threads.
Snapshots
The first seed or stream call installs the snapshot handler. Make that call before loading a snapshot. A load updates existing generators in place, so systems that captured a stream during setup retain the same object.
Ranges
local unit <const> = loot:next() -- [0, 1)
local d20 <const> = loot:integer(20) -- [1, 20]
local offset <const> = loot:integer(-3, 3) -- [-3, 3]
local speed <const> = loot:range(2.0, 5.0) -- [2, 5)integer includes both ends and rejects an empty range. range excludes its upper end.
Module contents
Constructors
| Constructor | Description |
|---|---|
newRandom |
Creates a generator outside every world and snapshot. |
Types
| Type | Kind | Description |
|---|---|---|
Random |
record | Represents a generator through four 32-bit words of xoshiro128 state and the number of draws taken from it. |
Functions
| Function | Kind | Description |
|---|---|---|
seed |
Static | Sets the world's seed and restarts every stream in it from that seed. |
stream |
Static | Returns the generator named name in world, creating it on the first call and returning the same object afterwards. |
Constructors
tecs.ecs.random.newRandom Static
Creates a generator outside every world and snapshot. Tools, benchmarks, and tests use this; games usually use stream.
A seed is read as a 32-bit word, so 2^31 and -2^31 name the same one.
function tecs.ecs.random.newRandom(seed: integer): RandomArguments
| Name | Type | Description |
|---|---|---|
seed |
integer |
Omitted takes a fixed constant, not a clock, so an unseeded generator still repeats run to run. |
Returns
| Type | Description |
|---|---|
Random |
A generator nothing else holds and nothing else knows about: two calls on one seed give two objects that draw the same sequence, each at its own pace. |
Types
tecs.ecs.random.Random record
Represents a generator through four 32-bit words of xoshiro128** state and the number of draws taken from it.
Not thread-safe, and not meant to be: a generator shared across threads would produce an order that depends on which one got there first, which is the whole thing this module exists to avoid. Give each thread a stream of its own.
record tecs.ecs.random.Random
integer: function(self, m: integer, n: integer): integer
next: function(self): number
range: function(self, lo: number, hi: number): number
reseed: function(self, seed: integer)
setState: function(self, words: {integer})
shuffle: function<T>(self, list: {T}): {T}
state: function(self): {integer}
endtecs.ecs.random.Random:integer Instance
Returns the next integer in [1, m], or in [m, n] when both are given. It includes both ends and advances the generator.
Errors on an empty range, because a caller asking for a number between 5 and 3 has a bug and a silent 5 hides it.
Arguments
| Name | Type | Description |
|---|---|---|
self |
Random |
|
m |
integer |
The upper end when it is the only argument, and the lower end when n follows it. The one-argument form counts from 1, so integer(0) is the empty range [1, 0] and raises. |
n |
integer |
The upper end, included. Nil takes the one-argument form. |
Returns
| Type | Description |
|---|---|
integer |
An integer in the range from one draw whatever the range is. The method scales and floors instead of resampling, so a width that does not divide 2^32 has a bias of about one part in 2^32. |
tecs.ecs.random.Random:next Instance
Returns the next value and advances the generator by one round.
function tecs.ecs.random.Random.next(self): numberArguments
| Name | Type | Description |
|---|---|---|
self |
Random |
Returns
| Type | Description |
|---|---|
number |
A multiple of 2^-32 in [0, 1). Zero is a value it takes; one is not. |
tecs.ecs.random.Random:range Instance
Returns the next value in [lo, hi) and advances the generator.
function tecs.ecs.random.Random.range(
self, lo: number, hi: number
): numberArguments
| Name | Type | Description |
|---|---|---|
self |
Random |
|
lo |
number |
The included end, whether or not it is the smaller of the two. |
hi |
number |
The excluded end. A reversed pair is not an error and is not swapped either: the draw is lo + next() * (hi - lo) and next never reaches one, so range(7, 3) draws from (3, 7] rather than [3, 7). |
Returns
| Type | Description |
|---|---|
number |
A value between the two ends, and lo exactly when the two are equal. |
tecs.ecs.random.Random:reseed Instance
Restarts the sequence from seed in place. The generator retains its identity, so existing holders draw from the restart.
function tecs.ecs.random.Random.reseed(self, seed: integer)Arguments
| Name | Type | Description |
|---|---|---|
self |
Random |
|
seed |
integer |
Read as a 32-bit word, as newRandom reads one, so 2^31 and -2^31 restart the same sequence. Zero is a seed like any other; the generator expands it instead of rejecting it. |
Returns
None.
tecs.ecs.random.Random:setState Instance
Puts four state words back, in place, so anything already holding this generator draws from what was put back.
function tecs.ecs.random.Random.setState(self, words: {integer})Arguments
| Name | Type | Description |
|---|---|---|
self |
Random |
|
words |
{integer} |
Exactly four, in the order state gives them, and each read as a 32-bit word, so a state result round-trips unchanged. Any other length raises, as does nil, as does all four being zero. |
Returns
None.
tecs.ecs.random.Random:shuffle Instance
Shuffles a table in place with Fisher-Yates and returns the same table. It advances the generator once per element past the first.
function tecs.ecs.random.Random.shuffle<T>(self, list: {T}): {T}Type Parameters
| Name | Constraint | Description |
|---|---|---|
T |
Arguments
| Name | Type | Description |
|---|---|---|
self |
Random |
The generator advances once per element after the first. |
list |
{T} |
The method reorders this list over 1..#list. A list of one or none stays unchanged and draws nothing, so its length determines how far a shared generator advances. |
Returns
| Type | Description |
|---|---|
{T} |
list itself rather than a copy, so the caller's own reference is already shuffled and the return is a convenience. |
tecs.ecs.random.Random:state Instance
Returns the four state words as plain integers that a snapshot, log line, or bug report can carry.
function tecs.ecs.random.Random.state(self): {integer}Arguments
| Name | Type | Description |
|---|---|---|
self |
Random |
Returns
| Type | Description |
|---|---|
{integer} |
A fresh four-element array each call, and a copy: writing to it does not move the generator, and the generator moving does not change it. |
Functions
tecs.ecs.random.seed Static
Sets the world's seed and restarts every stream in it from that seed.
Call it during setup. Calling it mid-run is well defined and rewinds everything, which is rarely what a caller means.
function tecs.ecs.random.seed(world: types.World, seed: integer)Arguments
| Name | Type | Description |
|---|---|---|
world |
types.World |
The call reseeds each existing stream in place, so a holder keeps the object. It also installs the snapshot handler, which is why games that load snapshots call it during setup. |
seed |
integer |
Read as a 32-bit word, as newRandom reads one. There is no way to ask a world for its seed back; take it from a snapshot instead. |
Returns
None.
tecs.ecs.random.stream Static
Returns the generator named name in world, creating it on the first call and returning the same object afterwards.
Names use dot namespaces like snapshot data keys. The engine uses tecs.audio and tecs.runif, so game names should carry their own prefix.
Arguments
| Name | Type | Description |
|---|---|---|
world |
types.World |
The first ask installs a snapshot handler named tecs.random on it. A world that loads a snapshot before anything has asked for a stream has no handler yet and drops the saved seed, which random.seed during setup avoids. |
name |
string |
Nil or empty raises. The module compares names byte for byte, so two spellings of the same idea create independent streams. |
Returns
| Type | Description |
|---|---|
Random |
The world's generator for this name, not a copy: two callers asking for one name draw from the same sequence, in the order they happen to ask. |