A fast, explicit random generation toolkit
Project description
Fortuna
Fortuna is a fast random-generation toolkit for Python 3.14. It combines an explicit generator model, deterministic stream derivation, native bulk generation, practical probability distributions, and prepared value selectors for games, simulations, procedural generation, and generative systems.
Fortuna 6 is built on the vendored, immutable Storm 5.0.2 engine and is licensed under MIT.
Toolchain-free installs on supported systems: Fortuna 6 ships precompiled CPython 3.14 wheels for macOS arm64 and x86-64, Linux arm64 and x86-64, and Windows x86-64. On those targets, pip and uv install Fortuna without a C++ compiler, platform SDK, or development toolchain. Windows support is notably improved: Visual Studio Build Tools are not required.
Not for cryptography: Fortuna uses MT19937-64. Do not use it for passwords, tokens, keys, secrets, authentication, cryptography, gambling security, or security decisions. Entropy-seeded construction does not make Fortuna cryptographically secure. Python's
secretsmodule is the standard library starting point for security-sensitive randomness.
Installation
Fortuna requires Python 3.14:
python -m pip install Fortuna
With uv:
uv add Fortuna
A source build requires a C++20 toolchain. Contributors working from a checkout should use the reproducible development setup in the development guide.
Five-minute tour
Fortuna's module functions are convenient when a process-local random stream is appropriate:
import Fortuna
initiative = Fortuna.d(20)
fireball_damage = Fortuna.dice(8, 6)
ability_scores = Fortuna.ability_dice(count=6)
morale_adjustment = Fortuna.plus_or_minus_triangular(2)
The corresponding methods on Generator provide explicit ownership and
repeatability:
world = Fortuna.Generator(0xD06E_0A5E)
room_count = world.random_int(5, 12)
room_sizes = world.random_int(3, 10, count=room_count)
Two generators with the same seed produce the same stable Fortuna-owned draw schedule:
first = Fortuna.Generator(42)
second = Fortuna.Generator(42)
assert first.dice(3, 6, count=20) == second.dice(3, 6, count=20)
Module defaults and explicit generators
Module-level calls use a Fortuna-owned thread-local generator initialized from process-local entropy. They are appropriate for convenient independent draws:
wand_charges = Fortuna.random_int(1, 20)
Use an explicit Generator when ownership, reproducibility, or worker stream
identity matters:
campaign_seed = 8128
encounters = Fortuna.for_stream(campaign_seed, "encounters")
treasure = Fortuna.for_stream(campaign_seed, "treasure")
encounter_roll = encounters.d(100)
treasure_roll = treasure.d(100)
for_stream(root_seed, stream_id) deterministically derives a generator from
an unsigned 64-bit root seed and an int, str, or bytes identifier. The
stream identifier is an input to seed derivation, not a decorative label, so
changing it produces a different deterministic generator sequence. Identifier
types are intentionally distinct.
Use from_entropy() when an explicitly owned but nondeterministic generator is
required:
generator = Fortuna.from_entropy()
seed(value) is deterministic, including seed(0). It changes only the
calling thread's module default. Other threads and existing explicit generators
keep their current states.
Scalar and bulk generation
Every numeric generation function and its Generator method accepts the
keyword-only argument count:
one_roll = Fortuna.d(20)
many_rolls = Fortuna.d(20, count=100_000)
one_sample = Fortuna.normal_variate(0.0, 1.0)
many_samples = Fortuna.normal_variate(0.0, 1.0, count=100_000)
Bulk generation performs the sampling loop in the native extension without
holding the Python GIL and returns a list. count=0 returns an empty list.
Arguments are validated before the engine advances.
The public numeric families include:
- Uniform booleans, integers, ranges, indexes, floats, and canonical draws.
- Dice, ability dice, and symmetric
plus_or_minusdistributions. - Triangular, normal, log-normal, exponential, gamma, Weibull, beta, Pareto, von Mises, binomial, negative-binomial, geometric, Poisson, extreme-value, chi-squared, Cauchy, Fisher F, and Student's t distributions.
- Front, center, and back triangular positional profiles.
The complete domains and failure contracts live in the API reference.
Collection operations
Fortuna provides uniform selection, sampling without replacement, and an in-place native Knuth-B shuffle:
monsters = ["goblin", "owlbear", "mimic", "dragon"]
monster = Fortuna.random_value(monsters)
patrol = Fortuna.sample(monsters, 2)
Fortuna.shuffle(monsters)
An explicit generator can be supplied to each module helper or used directly:
generator = Fortuna.Generator(7)
monster = Fortuna.random_value(monsters, generator=generator)
patrol = generator.sample(monsters, 2)
generator.shuffle(monsters)
Fortuna selects Knuth-B for its large-workload behavior. In controlled macOS arm64 release-build measurements it holds a substantial advantage at one million elements; at intermediate sizes the two traversals are close and can exchange small wins. Exact ratios remain machine- and build-specific.
Prepared value generation
Prepared value engines materialize their inputs once and remain ordinary Python callables. They are useful when the same table is drawn repeatedly.
RandomValue
RandomValue combines uniform, cyclic, wide, and positional selection behind
one small interface:
loot = Fortuna.RandomValue(["copper", "potion", "wand"])
uniform_drop = loot()
same_as_uniform = loot.uniform()
next_in_order = loot.cycle()
front_loaded_drop = loot.front_triangular()
center_loaded_drop = loot.center_triangular()
back_loaded_drop = loot.back_triangular()
wide_drop = loot.truffle_shuffle()
Its methods are normal bound callables. This supports the prepared-generator pattern directly:
loot_gen = Fortuna.RandomValue(["copper", "potion", "wand"]).front_triangular
drop = loot_gen()
more_drops = [loot_gen() for _ in range(20)]
take(count, ...) repeats the engine's default uniform strategy:
loot = Fortuna.RandomValue(["copper", "potion", "wand"])
treasure_pile = loot.take(10)
TruffleShuffle
TruffleShuffle is a stateful wide selector designed for broad uniformity with
reduced localized clumping. It shuffles the table once, then moves through that
permutation by randomized short distances:
encounter = Fortuna.TruffleShuffle(
["goblins", "wolves", "bandits", "skeletons", "giant spiders"]
)
next_encounter = encounter()
encounter_week = encounter.take(7)
TruffleShuffle allows repeats. Its design combines reduced localized repetition with broad long-run coverage. See Algorithm and design notes for the model and its origin.
WeightedChoice
WeightedChoice accepts finite, nonnegative relative weights:
rarity = Fortuna.WeightedChoice(
[
(80, "common"),
(18, "rare"),
(2, "legendary"),
]
)
item_rarity = rarity()
Weights are relative and may total any positive finite value. A weight of zero is allowed, and at least one weight must be positive. Native generators use Storm's prepared cumulative selector with logarithmic lookup for repeated draws.
Callable values
Prepared value engines resolve selected callables after selection. This makes nested procedural tables concise and evaluates only the selected path:
coins = Fortuna.RandomValue(
[
lambda: f"{Fortuna.dice(3, 6)} copper pieces",
lambda: f"{Fortuna.dice(2, 6)} silver pieces",
lambda: f"{Fortuna.d(6)} gold pieces",
]
)
treasure = coins()
Pass resolve_callables=False when callable objects are the intended values.
Callable cycles and runaway chains raise RuntimeError.
Positional profiles
The positional profiles use bounded triangular algorithms:
table = ["common", "uncommon", "rare", "very rare", "legendary"]
front = table[Fortuna.front_triangular(len(table))]
center = table[Fortuna.center_triangular(len(table))]
back = table[Fortuna.back_triangular(len(table))]
front_triangularuses the smaller of two uniform indexes.center_triangularuses their integer midpoint.back_triangularuses the larger index.
Process and thread behavior
Module defaults are thread-local. A forked child invalidates the inherited
module default and reseeds it before its next draw. A generator created through
from_entropy() behaves similarly.
Explicit deterministic generators intentionally preserve copied state through
fork. Derive a separate stream for each worker that needs a distinct
sequence:
worker_generator = Fortuna.for_stream(root_seed, worker_id)
Built-in operations sharing one exact Generator are serialized, but thread
scheduling still determines which thread receives each draw. Value engines
also contain mutable strategy state, so callers own their synchronization. Use
one value-engine instance per worker or synchronize it externally.
Fork only after concurrent use of a shared explicit generator has quiesced; a child could otherwise inherit its native mutex in a locked state.
Reproducibility boundary
Fortuna's bounded integer, index, range, dice, canonical, bounded-triangular, stream-derivation, uniform value-selection, sampling, and shuffle schedules are stable across supported platforms throughout the Fortuna 6 line.
Standard-library probability distributions, random_float, custom floating
transforms, TruffleShuffle's Poisson movement, and WeightedChoice's real
draw depend partly on C++ standard-library implementations. They are repeatable
within one platform and toolchain build. Their exact seeded sequences are
platform-and-toolchain-specific. See
Algorithm and design notes
for the complete boundary.
Documentation
- Practical guide: game-oriented examples and composition patterns.
- API reference: complete public surface, domains, and failure contracts.
- Algorithm and design notes: engine boundaries, TruffleShuffle, profiles, shuffle, and reproducibility.
- Migrating from Fortuna 5: renamed, changed, and removed interfaces.
- Development guide: build, test, benchmark, process, and contribution contracts.
- Changelog: release-level changes.
Development
uv sync --frozen --group dev --reinstall-package Fortuna
uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run pyright
uv run python -m benchmarks
uv build
Correctness and statistical validation live under tests/. Performance
measurement lives under benchmarks/; ordinary CI gates correctness, while
timing provides evidence for engineering decisions.
License
Fortuna is available under the MIT License. Its vendored Storm header is also MIT licensed; provenance and checksums are recorded in the vendoring record.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fortuna-6.0.4.tar.gz.
File metadata
- Download URL: fortuna-6.0.4.tar.gz
- Upload date:
- Size: 114.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f9000ca2b56a547bf3e0f104f662a51ed160920569dee155f6caceb3a5918551
|
|
| MD5 |
13d1b7c23f01fbd68cf9b20febe031ed
|
|
| BLAKE2b-256 |
22197a9167725280c904af46b155d34c347bbcc896d33d72ae88cbf830cf13d5
|
File details
Details for the file fortuna-6.0.4-cp314-cp314-win_amd64.whl.
File metadata
- Download URL: fortuna-6.0.4-cp314-cp314-win_amd64.whl
- Upload date:
- Size: 349.1 kB
- Tags: CPython 3.14, Windows x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
971f70a75f85c01da9de94f60ae040cbb0cc100ae543eda961a8ccf209511f09
|
|
| MD5 |
22582309abe11067c0901bd4db2972b3
|
|
| BLAKE2b-256 |
c76a09d32010324ceec92c47a659b7831dcd35a6272c020637ec295eb5fa7bfb
|
File details
Details for the file fortuna-6.0.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.
File metadata
- Download URL: fortuna-6.0.4-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
- Upload date:
- Size: 202.4 kB
- Tags: CPython 3.14, manylinux: glibc 2.24+ x86-64, manylinux: glibc 2.28+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
acb574cea4cdf13da9682bfc99ab8a7f95f88ab74f4238569f86a71e1a794846
|
|
| MD5 |
ff98ae609d7f05ccbe61a0d9823ffb7e
|
|
| BLAKE2b-256 |
2d55ab00c86821863a181a3d8374d5d9408565cd820a65b0a962044fb68337a1
|
File details
Details for the file fortuna-6.0.4-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl.
File metadata
- Download URL: fortuna-6.0.4-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl
- Upload date:
- Size: 187.0 kB
- Tags: CPython 3.14, manylinux: glibc 2.24+ ARM64, manylinux: glibc 2.28+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0b00c207f9921982551dbac90286d285d962c61ee82d499cccfe09d077d3a4a5
|
|
| MD5 |
5b01ae4e301252a846d052157efd5190
|
|
| BLAKE2b-256 |
edb753f163192e01ef87198ba86314c4a8565453f3ce349f0b6b1ead3c32d23d
|
File details
Details for the file fortuna-6.0.4-cp314-cp314-macosx_11_0_arm64.whl.
File metadata
- Download URL: fortuna-6.0.4-cp314-cp314-macosx_11_0_arm64.whl
- Upload date:
- Size: 159.9 kB
- Tags: CPython 3.14, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3e9d309a59f67457a6992b46d9a8251b1853241d910454f9c41c2bd4de5bc318
|
|
| MD5 |
bb15d600db25bf5e389b779daa8cee59
|
|
| BLAKE2b-256 |
b61f60a591ffd8c8c15b568018e7a7fead289fa006dd462e34e44db568638f62
|
File details
Details for the file fortuna-6.0.4-cp314-cp314-macosx_10_15_x86_64.whl.
File metadata
- Download URL: fortuna-6.0.4-cp314-cp314-macosx_10_15_x86_64.whl
- Upload date:
- Size: 181.3 kB
- Tags: CPython 3.14, macOS 10.15+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.28 {"installer":{"name":"uv","version":"0.11.28","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2ad2c4dfc74a8e505f7652deae5110f6f5607e19b7211ca733480c28c16cb3c0
|
|
| MD5 |
b7c1d6318365e3991d442a7cfd9582f8
|
|
| BLAKE2b-256 |
ba999bb6bbeab82a7b4736fc2a10bf02981fdee7ff1e10976ab85c2727fbc109
|