---
name: set-anima-randomness
description: AnimaSystemRandomness, how Set decides every value it does not fix. It covers which results replay from a seed, which draw real randomness from a cryptographic source, and which never change and are computed once when Set is built. Use when a result must be reproducible or must not be, when replaying a roll, craft, drop or fight, when choosing the random generator for a game built on Set, when random results must be faster or more truly random, or when deciding what a game can compute once instead of every time.
license: "Free to use when obtained from set.world, commercially too (the API, what it returns and the skills). Its models, animations and textures, Anima included, are CC BY 4.0: free to use with attribution to Jimmy Lee (j@internet.dev). The source code of Set is © Jimmy Lee under CC BY-NC 4.0 and its commercial use needs a paid waiver."
metadata:
  version: "0.15.3"
  title: Randomness
---

# Randomness (AnimaSystemRandomness)

AnimaSystemRandomness (`set-world-0-15-3-randomness-1`) is how Set decides everything it does not fix. Every value Set gives you is one of three kinds, and never a mix of two:

| Kind | What it is | Where it comes from | Replays |
| --- | --- | --- | --- |
| **Fixed** | The same for everyone, every time: a table, a price, a grade's odds, an item model's weight and reach, a hand's grip | Set's code, tables and models, computed once when Set is built | Always, for the same release and `contentVersion` |
| **Seeded** | Looks random, but is reproducible: a roll, craft, drop or fight you started from a seed | `mulberry32`, a 32-bit generator started from the seed | For the same operation, inputs, seed and `contentVersion` ([API connection](https://set.world/skills/api-connection)) |
| **Fresh** | Real randomness: no one, Set included, can predict or repeat it | The platform's cryptographic random source, `crypto.getRandomValues` | Never: keep the result |

Set wants real randomness. A fresh result is drawn from a cryptographic source, never from a language's default generator. Making random results faster to draw, or more truly random, is a standing priority of this system. Speed never changes what a seed produces: a faster draw spends the stream exactly as the slower one did, or it is a new `contentVersion`.

## Ask for the kind you want

- **Fresh.** Omit `seed`: `GET /api/roll/character` draws every number from the server's cryptographic source and answers `"seed": null`. Keep the whole response, because nothing can roll it again. The same holds for `/api/roll/item`, `/api/roll/set`, `/api/craft`, `/api/drop` and `/api/basic-lightweight-combat`.
- **Seeded.** Pass `seed`, from 0 to 4294967295: `GET /api/roll/character?seed=7` returns the same character every time for `contentVersion` `7.1bmmTgz1B0Y9UKhiu84STg`. To expand one seed into a party or an army, use `deriveSeed` ([Rolling a group](https://set.world/skills/rolling-a-group)).
- **Fresh, then replayable.** Draw a fresh seed yourself and pass it. Set's own pages do this: an empty seed field, **Recycle**, a new army, `/anima-scene` when it opens (each Anima it places is a `deriveSeed` member of that seed) and every new fight each draw one fresh 32-bit seed, then roll seeded, so whatever they keep can be replayed.
- **Fixed.** Read it. There is nothing to choose.

## How a fresh number is drawn

Set keeps a pool of 16,384 words of 32 bits, filled by `crypto.getRandomValues`, and refills it only when every word is spent. A draw is then a read from memory, not a call into the platform. A number in [0, 1) takes two words and keeps 53 random bits, the full precision of a double; a seed takes one word.

```js
const pool = new Uint32Array(16384);
let next = pool.length;
const word = () => {
  if (next === pool.length) { crypto.getRandomValues(pool); next = 0; }
  return pool[next++];
};
const freshRandom = () => ((word() >>> 5) * 2 ** 26 + (word() >>> 6)) / 2 ** 53;   // 27 + 26 bits
const freshSeed = () => word();                                    // a uint32, 0 to 4294967295
```

A whole number below `n` is `Math.floor(freshRandom() * n)`, as Set's weighted tables draw. Rounding moves an outcome's chance by less than `n / 2^53` of its own share. A seeded draw keeps 32 bits, so there rounding moves an outcome's chance by up to `n / 2^32` of its share: still small, and kept as it is, because changing it would change what every seed produces.

## How a seeded stream runs

`mulberry32` turns a seed into a stream of numbers `k / 2^32`. Its exact steps are in §6.1 of [Algorithms](https://set.world/skills/algorithms); `deriveSeed` is in [Rolling a group](https://set.world/skills/rolling-a-group). Set gives every purpose its own stream, so a draw added to one purpose never shifts another:

| Stream | Started from | Decides | Owner |
| --- | --- | --- | --- |
| A roll, craft, drop or combat call | The request's `seed` | The whole result, in draw order | [API connection](https://set.world/skills/api-connection) |
| A roster member | `deriveSeed(seed, i)` | That member | [Rolling a group](https://set.world/skills/rolling-a-group) |
| A scene's level and orb projection | A hash of the birth's seed and `contentVersion` | That Anima's stat growth and the items its orbs buy | [Derived stats](https://set.world/skills/anima-derived-stats) |
| What an Anima releases on death | A hash of its seed and name | Its drop | [Derived stats](https://set.world/skills/anima-derived-stats) |
| A model's designed team | A hash of each birth's seed and `contentVersion` | Where in each entry the designer chose its raw roll falls | [Scenes](https://set.world/skills/anima-scenes) |
| A fight | A fresh fight seed, drawn whenever a fight starts | Three streams: actions, movement and visuals | [Scenes](https://set.world/skills/anima-scenes) |
| An adventure | A hash of the party lead's name, seed and `contentVersion` | The maze, the enemies and every combat draw | [Derived combat](https://set.world/skills/anima-derived-combat) |

Set draws the game's outcomes from these streams only, and nothing in Set reads a language's default generator. Purely cosmetic noise (the sparks and flame particles in the scenes) runs on streams of its own, each started from a fresh seed when its page loads, so it can never shift an outcome. The craft stone's look draws its own fresh seed, apart from the craft.

## Fixed: computed when Set is built

A value that depends only on Set's code, tables and models (no seed, no input, no screen) is computed once when Set is built. The live service then reads it instead of computing it again:

| Prepared when Set is built | What it saves |
| --- | --- |
| `contentVersion` | Hashing every table each time a server starts |
| The salvage sink's worst return per orb ([Crafting an item](https://set.world/skills/crafting-an-item)) | Walking every spend from 1 to 15,625 orbs each time a server starts |
| Every held item model's mass, grip inertia, reach, size and bolt ([Item models](https://set.world/skills/anima-item-models)) | Building each model in the browser just to measure it |
| The fitted blade grip, each fitted hand length and both hands' finger closure on every handle size | Solving finger closure in the browser on the first wield |
| Every value these skills print | Measuring and computing them on each request |

Fixed values too cheap to be worth a file are computed once per process and kept: each table's weight total, cumulative weights, entry probabilities, grade shares and material grades. On a page, each Anima's eye and tattoo decals are built once and copied. Fixed values too large to ship are computed once per page, in idle time or when a scene first needs them ([Orchestration](https://set.world/skills/anima-orchestration)): an Anima's body measurements, its sampled motion and the floor texture. Anything that reads a seed, an input, a level, a material, a quality or the screen stays live.

## The record of how a value was made

Set keeps no such record; this one is the game's own, labelled hypothetical:

```ts
type Randomness =
  | { kind: 'fixed'; release: string; contentVersion: string }   // release: the skills' metadata.version
  | { kind: 'seeded'; seed: number; contentVersion: string }     // seed: 0 to 4294967295
  | { kind: 'fresh' };                                           // nothing to replay

type Drawn<T> = { value: T; randomness: Randomness };
```

A seeded enemy drop, `GET /api/drop?stats=12-14-10-11-13-16-12-14-11&class=elite&seed=7` ([Crafting an item](https://set.world/skills/crafting-an-item)), kept so it can be replayed:

```json
{
  "value": {
    "seed": 7,
    "stats": [12, 14, 10, 11, 13, 16, 12, 14, 11],
    "avgStat": 12.556,
    "class": "elite",
    "baseDrop": 11,
    "classMultiplier": 10,
    "rolledVariance": 0.7559,
    "orbs": 83,
    "override": null,
    "contentVersion": "7.1bmmTgz1B0Y9UKhiu84STg"
  },
  "randomness": { "kind": "seeded", "seed": 7, "contentVersion": "7.1bmmTgz1B0Y9UKhiu84STg" }
}
```

## Rules

- Seed anything someone may want to replay, share or check. Leave unseeded only what nobody should be able to predict, and keep its whole result.
- One purpose, one stream. Derive a stream per purpose from a fresh seed (`deriveSeed(seed, k)`); never share a stream between outcomes and visuals.
- A faster draw spends the stream exactly as before. A change that would make one seed produce another result moves `contentVersion`.
- A fixed value is computed when Set is built, or once and kept, and never again per request.

## What stays the game's decision

Whether to pass a seed, which seeds to keep and where, the generator your own rules use (the worked function in the [public guide](https://set.world/AGENTS.md) takes one), and what in your game counts as cosmetic.

## Limits

- A seed is not a secret. `mulberry32` is predictable from its output and has 32-bit seeds; use it for replay, never for security.
- A fresh result is unpredictable but unprovable: nothing shows afterwards that a fresh roll was fair, because only the server saw it drawn. When proof matters, draw the seed yourself and pass it.
- Fixed values are fixed per release. A new release may change a model or a rule, and with it a fixed value; `contentVersion` moves only when generation does.
