# tecs.ecs.random Seeded generation, in named streams a snapshot carries. ```teal tecs.ecs.random.seed(world, 20260726) local loot = tecs.ecs.random.stream(world, "game.loot") local spawns = tecs.ecs.random.stream(world, "game.spawns") local roll = loot:integer(1, 20) ``` ## Streams The world seed and stream name determine each [`Random`](/modules/ecs/random/#tecs.ecs.random.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 ```teal local unit = loot:next() -- [0, 1) local d20 = loot:integer(20) -- [1, 20] local offset = loot:integer(-3, 3) -- [-3, 3] local speed = 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`](/modules/ecs/random/#tecs.ecs.random.newRandom) | Creates a generator outside every world and snapshot. | ### Types | Type | Kind | Description | | --- | --- | --- | | [`Random`](/modules/ecs/random/#tecs.ecs.random.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`](/modules/ecs/random/#tecs.ecs.random.seed) | Static | Sets the world's seed and restarts every stream in it from that seed. | | [`stream`](/modules/ecs/random/#tecs.ecs.random.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. ```teal function tecs.ecs.random.newRandom(seed: integer): Random ``` #### Arguments | 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`](/modules/ecs/random/#tecs.ecs.random.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. ```teal 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(self, list: {T}): {T} state: function(self): {integer} end ``` #### tecs.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. ```teal function tecs.ecs.random.Random.integer( self, m: integer, n: integer ): integer ``` ##### 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. ```teal function tecs.ecs.random.Random.next(self): number ``` ##### Arguments | 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. ```teal function tecs.ecs.random.Random.range( self, lo: number, hi: number ): number ``` ##### Arguments | 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. ```teal 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. ```teal 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. ```teal function tecs.ecs.random.Random.shuffle(self, list: {T}): {T} ``` ##### Type Parameters | Name | Constraint | Description | | --- | --- | --- | | `T` | | | ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | [`Random`](/modules/ecs/random/#tecs.ecs.random.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. ```teal 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. ```teal function tecs.ecs.random.seed(world: types.World, seed: integer) ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `world` | [`types.World`](/modules/ecs/#tecs.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. ```teal function tecs.ecs.random.stream( world: types.World, name: string ): Random ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `world` | [`types.World`](/modules/ecs/#tecs.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`](/modules/ecs/random/#tecs.ecs.random.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. |